diff --git a/ui/src/api/API_README.md b/ui/src/api/API_README.md index f8243b70525..c68b19cd04f 100644 --- a/ui/src/api/API_README.md +++ b/ui/src/api/API_README.md @@ -16,17 +16,18 @@ src/api/ │ ├── system/ # 系统管理业务接口 │ │ ├── chat-user/ # 对话用户、用户组及认证接口 │ │ ├── settings/ # 登录认证、邮件与外观设置接口 +│ │ ├── resource-management/ # System 资源管理接口 │ │ ├── shared-resources/ # System 共享资源接口 │ │ └── .ts # 其他 System 单资源接口 │ ├── workspace/ # 工作空间业务接口 │ │ ├── conversation.ts # 调试对话、历史会话与语音接口 │ │ ├── application/ # 智能体接口 │ │ ├── knowledge/ # 知识库接口 -│ │ ├── model/ # 模型接口 +│ │ ├── model.ts # 模型接口 │ │ ├── trigger/ # 触发器查询及维护接口 │ │ ├── tool/ # 工具、工具工作流及工具商店接口 │ │ └── .ts # 工作空间公共资源接口 -│ └── provider.ts # Workspace 与 System 共用的模型供应商接口 +│ └── model-provider.ts # Workspace 与 System 共用的模型供应商接口 ├── chat/ # Chat 独立请求体系 │ ├── core/request.ts # JSON 与流式请求 │ ├── core/types.ts # Chat 请求协议类型 @@ -136,7 +137,7 @@ v3 编辑表单提交 `{ name, description }`,标题上限 64、更新说明 ### 模型选项查询 -`workspace/model/model.ts` 的 `getModelListWithShared(query)` 请求 +`workspace/model.ts` 的 `getModelListWithShared(query)` 请求 `/workspace//model_list`,支持 `name`、`model_type`、`model_name` 筛选。 将 `shared_model` 与 `model` 按共享在前的顺序合并为 `ModelItem[]`,分别标记 `source: 'shared'` 与 `source: 'workspace'`。`SelectModel` 的接口选项统一通过此方法查询, @@ -149,6 +150,13 @@ v3 编辑表单提交 `{ name, description }`,标题上限 64、更新说明 将响应的 `tools` 与 `shared_tools` 合并为 `ToolItem[]`,用于包含已授权共享工具的选项查询; 按工具类型筛选时使用 `tool_type`。`workspace/shared.ts` 的 `getAllTool(query)` 仅查询共享工具。 +### System 共享工具 + +`system/shared-resources/tool/tool.ts` 使用 `/system/shared/tool`,维护分页、详情、创建、更新、删除、 +导入导出、连接测试、代码检查与调试、Skill 上传下载。路径沿用 v2 System 共享工具协议。 +共享工具 API 不提供工作空间文件夹移动或批量接口;复用卡片、Action 和表单接收完整 Workspace +与 System 共享 API 的联合类型,不补造接口以匹配 Workspace 方法集合。 + ### 知识库维护 `getKnowledgeMcpConfig(knowledgeId)` 与 `postKnowledgeKeywordIndex(knowledgeId)` 为预留接口方法, @@ -301,13 +309,17 @@ API 对象和工作空间上下文,作为该抽屉的范围选择例外;用 ### 关联资源 `admin/workspace/related-resources.ts` 维护关联资源查询: -`getResourceDependencies` 查询当前资源依赖的资源,对应后端 `mapping_resource`; -`getResourceDependents` 查询引用当前资源的资源,对应后端 `resource_mapping`。 -前端按关联资源语义命名,后端接口路径保持不变。方法接收工作空间 ID、资源类型、资源 ID、`ParamsPage` -和查询参数;工作空间 ID 由目标资源数据的 `workspace_id` 显式传入,不读取路由。返回 `ResponsePage`, +`getUsingResources` 查询当前资源依赖的资源,对应后端 `mapping_resource`; +`getUsedByResources` 查询引用当前资源的资源,对应后端 `resource_mapping`。 +前端按关联资源语义命名,后端接口路径保持不变。方法接收资源类型、资源 ID、`ParamsPage` +和查询参数;Workspace 接口内部通过 `getWorkspaceId()` 读取当前路由工作空间,调用方不传工作空间 ID。返回 `ResponsePage`, 不传 loading,不重复解包响应。`RelatedResource` 通过 `@/api/types` 导出,保留 `source_*`、`target_*` 字段。依赖查询按 `target_type` 筛选,被依赖查询按 `source_type` -筛选,类型数组沿用请求层的数组序列化。页面负责按资源范围传入完整 API,未推测 System 接口。 +筛选,类型数组沿用请求层的数组序列化。页面负责按资源范围传入完整 API。 + +`system/shared-resources/related-resources.ts` 沿用 v2 `/system/shared/resource_mapping` 和 +`/system/shared/mapping_resource`,方法只接收资源类型、资源 ID、分页和查询条件,不接收 +`workspaceId`。工作空间筛选继续通过 `query.workspace_ids` 传入;两类 API 使用相同参数签名,抽屉直接调用页面传入的完整 API。 ### 触发器维护 @@ -421,3 +433,25 @@ API 对象和工作空间上下文,作为该抽屉的范围选择例外;用 资源类型使用 `@/api/enums` 的 `FILE_SOURCE_TYPE` 与 `@/api/types` 的 `FileSourceType`, 包括知识库、智能体、工具、文档、对话及三种临时文件有效期。对话 Store 使用 `FILE_SOURCE_TYPE.CHAT` 调用对应 File API,对话 API 不再维护上传接口。 + +### System 资源管理模型与工具 + +`views/system/resource-management/` 页面导入的 API 对象统一使用 `System` 前缀, +例如 `SystemModelApi`、`SystemToolApi`、`SystemToolWorkflowApi`、`SystemRelatedResourcesApi`、 +`SystemResourceTriggerApi`、`SystemCommonApi`、`SystemWorkspaceApi` 和 `SystemModelProviderApi`。 +此处前缀用于区分页面中的 API 引用;共用供应商接口仍使用原有模块和请求地址。 + +`admin/system/resource-management/model.ts`、`tool/tool.ts` 沿用 v2 已有的 +`/system/resource/model` 与 `/system/resource/tool`,适配 v3 请求解包与命名。 +分页使用 `ParamsPage` / `ResponsePage`;模型、工具的 `workspace_name`、`resource_count` 和 +更新时间维护在各自公共业务类型中。页面维护 loading,不传入请求方法。 +模型选项使用 `/model/model_list` 并合并 `shared_model`、`model`;参数表单与配置更新使用 +资源自身 ID。工具仅提供既有的查询、编辑、删除、导出、调试、连接测试、代码检查及 Skill 文件接口, +不声明资源管理范围不存在的创建、导入接口。 + +`tool/tool-workflow.ts` 独立维护 System 工具工作流详情、保存、发布、调试、版本历史和执行记录, +协议与 Workspace 工作流接口一致。`related-resources.ts` 使用 +`/system/resource/resource_mapping` 与 `/system/resource/mapping_resource`,参数签名与 +当前 Workspace 关联资源 API 一致。`resource-trigger.ts` 使用 +`/system/resource///trigger`,资源上下文与触发器载荷复用公共类型。 +System 接口由资源管理服务提供;本地开源后端没有对应扩展时,需要连接提供这些接口的部署。 diff --git a/ui/src/api/admin/system/resource-management/model.ts b/ui/src/api/admin/system/resource-management/model.ts new file mode 100644 index 00000000000..2d33fe88a73 --- /dev/null +++ b/ui/src/api/admin/system/resource-management/model.ts @@ -0,0 +1,37 @@ +import { del, get, post, put } from '../../core/request' +import type { ParamsPage, ResponsePage } from '../../core/types' +import type { Dict, DynamicFormField, ModelItem, ModelPayload } from '@/api/types' + +const prefix = '/system/resource/model' + +/** 获取跨工作空间的模型分页列表。 */ +const getModelPage = (page: ParamsPage, query?: Dict) => + get>(`${prefix}/${page.currentPage}/${page.pageSize}`, query) + +/** 获取系统资源范围的模型及共享模型选项。 */ +const getModelListWithShared = (query?: Dict): Promise => + get<{ shared_model: ModelItem[]; model: ModelItem[] }>(`${prefix}/model_list`, query).then(({ shared_model, model }) => [ + ...shared_model.map((model): ModelItem => ({ ...model, source: 'shared' })), + ...model.map((model): ModelItem => ({ ...model, source: 'workspace' })), + ]) + +/** 获取包含认证信息的模型详情。 */ +const getModelDetail = (modelId: string) => get(`${prefix}/${modelId}`) + +/** 创建系统资源模型。 */ +const postModel = (payload: ModelPayload) => post(prefix, payload) + +/** 更新模型配置。 */ +const putModel = (modelId: string, payload: Partial) => put, ModelItem>(`${prefix}/${modelId}`, payload) + +/** 删除模型。 */ +const deleteModel = (modelId: string) => del(`${prefix}/${modelId}`) + +/** 获取模型参数表单。 */ +const getModelParamsForm = (modelId: string) => get(`${prefix}/${modelId}/model_params_form`) + +/** 保存模型参数表单。 */ +const putModelParamsForm = (modelId: string, payload: DynamicFormField[]) => + put(`${prefix}/${modelId}/model_params_form`, payload) + +export default { getModelPage, getModelListWithShared, getModelDetail, postModel, putModel, deleteModel, getModelParamsForm, putModelParamsForm } diff --git a/ui/src/api/admin/system/resource-management/related-resources.ts b/ui/src/api/admin/system/resource-management/related-resources.ts new file mode 100644 index 00000000000..e636c5073fd --- /dev/null +++ b/ui/src/api/admin/system/resource-management/related-resources.ts @@ -0,0 +1,15 @@ +import { get } from '../../core/request' +import type { ParamsPage, ResponsePage } from '../../core/types' +import type { Dict, RelatedResource, ResourceType } from '@/api/types' + +/** 获取引用当前资源的资源。 */ +const getUsedByResources = (resource: ResourceType, resourceId: string, page: ParamsPage, query?: Dict) => { + return get>(`/system/resource/resource_mapping/${resource}/${resourceId}/${page.currentPage}/${page.pageSize}`, query) +} + +/** 获取当前资源依赖的资源。 */ +const getUsingResources = (resource: ResourceType, resourceId: string, page: ParamsPage, query?: Dict) => { + return get>(`/system/resource/mapping_resource/${resource}/${resourceId}/${page.currentPage}/${page.pageSize}`, query) +} + +export default { getUsedByResources, getUsingResources } diff --git a/ui/src/api/admin/system/resource-management/resource-trigger.ts b/ui/src/api/admin/system/resource-management/resource-trigger.ts new file mode 100644 index 00000000000..660fb6c6c42 --- /dev/null +++ b/ui/src/api/admin/system/resource-management/resource-trigger.ts @@ -0,0 +1,24 @@ +import { get, post, put, del } from '../../core/request' +import type { ResourceTrigger, ResourceTriggerDetail, ResourceTriggerResource, TriggerPayload } from '@/api/types' + +const getPrefix = (resource: ResourceTriggerResource) => `/system/resource/${resource.source_type}/${resource.source_id}/trigger` + +/** 查询当前资源关联的已启用触发器。 */ +const getResourceTriggerList = (resource: ResourceTriggerResource) => get(getPrefix(resource)) + +/** 查询触发器配置及当前资源的执行任务。 */ +const getResourceTriggerDetail = (resource: ResourceTriggerResource, triggerId: string) => + get(`${getPrefix(resource)}/${triggerId}`) + +/** 为当前资源创建只有一个执行任务的触发器。 */ +const postResourceTrigger = (resource: ResourceTriggerResource, payload: TriggerPayload) => + post(getPrefix(resource), payload) + +/** 更新触发器配置及当前资源的任务参数,保留其他资源任务。 */ +const putResourceTrigger = (resource: ResourceTriggerResource, triggerId: string, payload: TriggerPayload) => + put(`${getPrefix(resource)}/${triggerId}`, payload) + +/** 移除当前资源与触发器的关联,最后一个任务移除时删除触发器。 */ +const deleteResourceTrigger = (resource: ResourceTriggerResource, triggerId: string) => del(`${getPrefix(resource)}/${triggerId}`) + +export default { getResourceTriggerList, getResourceTriggerDetail, postResourceTrigger, putResourceTrigger, deleteResourceTrigger } diff --git a/ui/src/api/admin/system/resource-management/tool/tool-workflow.ts b/ui/src/api/admin/system/resource-management/tool/tool-workflow.ts new file mode 100644 index 00000000000..fcf5cc89635 --- /dev/null +++ b/ui/src/api/admin/system/resource-management/tool/tool-workflow.ts @@ -0,0 +1,72 @@ +import type LogicFlow from '@logicflow/core' +import { get, put, postStream } from '../../../core/request' +import type { ParamsPage, ResponsePage } from '../../../core/types' +import type { + DefaultModelSettingPayload, + Dict, + ToolExecutionRecord, + ToolExecutionRecordDetail, + ToolWorkflowDetail, + ToolWorkflowRecord, + WorkflowStoreTemplate, + WorkflowVersion, + WorkflowVersionPayload, +} from '@/api/types' +import { ADMIN_API_BASE_PATH } from '@/api/constants' + +type ToolWorkflowPayload = + | { default_model_setting?: DefaultModelSettingPayload; work_flow: LogicFlow.GraphConfigData; work_flow_template?: never } + | { work_flow_template: WorkflowStoreTemplate; work_flow?: never } + +const prefix = '/system/resource/tool' + +/** 获取工具工作流详情。 */ +const getToolWorkflow = (toolId: string) => { + return get(`${prefix}/${toolId}/workflow`) +} + +/** 保存工具工作流,或使用商店模板覆盖当前工作流。 */ +const putToolWorkflow = (toolId: string, payload: ToolWorkflowPayload) => { + return put(`${prefix}/${toolId}/workflow`, payload) +} + +/** 发布工具工作流。 */ +const putToolWorkflowPublish = (toolId: string) => { + return put(`${prefix}/${toolId}/publish`) +} + +/** 调试已保存的工具工作流,返回 SSE 响应。 */ +const postToolWorkflowDebug = (toolId: string, parameters: Record) => + postStream(ADMIN_API_BASE_PATH, `${prefix}/${toolId}/debug`, parameters) + +/** 查询工具工作流调试的输出和节点执行记录。 */ +const getToolWorkflowRecord = (toolId: string, recordId: string) => get(`${prefix}/${toolId}/tool_record/${recordId}`) + +/** 获取工具发布历史,按发布时间倒序返回完整版本快照。 */ +const getWorkflowVersions = (toolId: string) => get(`${prefix}/${toolId}/tool_version`) + +/** 修改工具历史版本标题和更新说明,更新说明需要服务端支持 description 字段。 */ +const putWorkflowVersion = (toolId: string, versionId: string, data: WorkflowVersionPayload) => + put(`${prefix}/${toolId}/tool_version/${versionId}`, data) + +/** 获取工具执行记录分页,按执行时间倒序返回。 */ +const getToolExecutionRecordPage = (toolId: string, page: ParamsPage, query?: Dict) => { + return get>(`${prefix}/${toolId}/tool_record/${page.currentPage}/${page.pageSize}`, query) +} + +/** 获取工具执行记录的输入输出及节点详情。 */ +const getToolExecutionRecordDetail = (toolId: string, recordId: string) => { + return get(`${prefix}/${toolId}/tool_record/${recordId}`) +} + +export default { + getToolExecutionRecordPage, + getToolExecutionRecordDetail, + getWorkflowVersions, + putWorkflowVersion, + getToolWorkflow, + putToolWorkflow, + putToolWorkflowPublish, + postToolWorkflowDebug, + getToolWorkflowRecord, +} diff --git a/ui/src/api/admin/system/resource-management/tool/tool.ts b/ui/src/api/admin/system/resource-management/tool/tool.ts new file mode 100644 index 00000000000..c42ccd5b506 --- /dev/null +++ b/ui/src/api/admin/system/resource-management/tool/tool.ts @@ -0,0 +1,70 @@ +import { del, downloadRequest, getExportFile, get, post, put } from '../../../core/request' +import type { ParamsPage, ResponsePage } from '../../../core/types' +import type { Dict, ToolDebugPayload, ToolItem, ToolPayload, ToolPylintIssue } from '@/api/types' + +const prefix = '/system/resource/tool' + +/** 获取工具分页列表。 */ +const getToolPage = (page: ParamsPage, query?: Dict) => { + return get>(`${prefix}/${page.currentPage}/${page.pageSize}`, query) +} + +/** 删除System 资源工具。 */ +const deleteTool = (toolId: string) => { + return del(`${prefix}/${toolId}`) +} + +/** 更新System 资源工具。 */ +const putTool = (toolId: string, payload: ToolPayload) => { + return put(`${prefix}/${toolId}`, payload) +} + +/** 检查System 资源工具的 Python 代码。 */ +const postToolPylint = (code: string) => { + return post<{ code: string }, ToolPylintIssue[]>(`${prefix}/pylint`, { code }) +} + +/** 调试普通工具代码并返回运行结果。 */ +const postToolDebug = (payload: ToolDebugPayload) => { + return post(`${prefix}/debug`, payload) +} + +/** 获取工具详情。 */ +const getToolDetail = (toolId: string) => { + return get(`${prefix}/${toolId}`) +} + +/** 上传 Skill 压缩包并返回临时文件 ID。 */ +const putUploadSkillFile = (file: File) => { + const payload = new FormData() + payload.append('file', file) + return put(`${prefix}/upload_skill_file`, payload) +} + +/** 下载 Skill 工具的压缩包。 */ +const downloadSkillFile = (toolId: string) => { + return downloadRequest(`${prefix}/${toolId}/download_skill_file`, 'GET') +} + +/** 导出System 资源工具文件。 */ +const exportTool = (toolId: string, toolName: string) => { + return getExportFile(`${toolName}.tool`, `${prefix}/${toolId}/export`) +} + +/** 测试工具配置是否可连接。 */ +const postToolTestConnection = (payload: ToolPayload) => { + return post(`${prefix}/test_connection`, payload) +} + +export default { + getToolPage, + exportTool, + deleteTool, + getToolDetail, + downloadSkillFile, + postToolDebug, + putUploadSkillFile, + postToolPylint, + postToolTestConnection, + putTool, +} diff --git a/ui/src/api/admin/system/shared-resources/related-resources.ts b/ui/src/api/admin/system/shared-resources/related-resources.ts new file mode 100644 index 00000000000..bc99e02f5fe --- /dev/null +++ b/ui/src/api/admin/system/shared-resources/related-resources.ts @@ -0,0 +1,17 @@ +import { get } from '../../core/request' +import type { ParamsPage, ResponsePage } from '../../core/types' +import type { Dict, RelatedResource, ResourceType } from '@/api/types' + +const prefix = '/system/shared' + +/** 获取引用当前资源的资源。 */ +const getUsedByResources = (resource: ResourceType, resourceId: string, page: ParamsPage, query?: Dict) => { + return get>(`${prefix}/resource_mapping/${resource}/${resourceId}/${page.currentPage}/${page.pageSize}`, query) +} + +/** 获取当前资源依赖的资源。 */ +const getUsingResources = (resource: ResourceType, resourceId: string, page: ParamsPage, query?: Dict) => { + return get>(`${prefix}/mapping_resource/${resource}/${resourceId}/${page.currentPage}/${page.pageSize}`, query) +} + +export default { getUsedByResources, getUsingResources } diff --git a/ui/src/api/admin/system/shared-resources/tool/tool.ts b/ui/src/api/admin/system/shared-resources/tool/tool.ts new file mode 100644 index 00000000000..d5ba7a771ef --- /dev/null +++ b/ui/src/api/admin/system/shared-resources/tool/tool.ts @@ -0,0 +1,85 @@ +import { del, downloadRequest, getExportFile, get, post, put } from '../../../core/request' +import type { ParamsPage, ResponsePage } from '../../../core/types' +import type { Dict, ToolDebugPayload, ToolItem, ToolPayload, ToolPylintIssue } from '@/api/types' + +const prefix = '/system/shared/tool' + +/** 获取工具分页列表。 */ +const getToolPage = (page: ParamsPage, query?: Dict) => { + return get>(`${prefix}/${page.currentPage}/${page.pageSize}`, query) +} + +/** 删除System 共享工具。 */ +const deleteTool = (toolId: string) => { + return del(`${prefix}/${toolId}`) +} + +/** 创建System 共享工具。 */ +const postTool = (payload: ToolPayload) => { + return post(prefix, payload) +} + +/** 更新System 共享工具。 */ +const putTool = (toolId: string, payload: ToolPayload) => { + return put(`${prefix}/${toolId}`, payload) +} + +/** 检查System 共享工具的 Python 代码。 */ +const postToolPylint = (code: string) => { + return post<{ code: string }, ToolPylintIssue[]>(`${prefix}/pylint`, { code }) +} + +/** 调试普通工具代码并返回运行结果。 */ +const postToolDebug = (payload: ToolDebugPayload) => { + return post(`${prefix}/debug`, payload) +} + +/** 获取工具详情。 */ +const getToolDetail = (toolId: string) => { + return get(`${prefix}/${toolId}`) +} + +/** 导入工具文件并创建System 共享工具。 */ +const postToolImport = (file: File, folderId: string) => { + const payload = new FormData() + payload.append('file', file) + if (folderId) payload.append('folder_id', folderId) + return post(`${prefix}/import`, payload) +} + +/** 上传 Skill 压缩包并返回临时文件 ID。 */ +const putUploadSkillFile = (file: File) => { + const payload = new FormData() + payload.append('file', file) + return put(`${prefix}/upload_skill_file`, payload) +} + +/** 下载 Skill 工具的压缩包。 */ +const downloadSkillFile = (toolId: string) => { + return downloadRequest(`${prefix}/${toolId}/download_skill_file`, 'GET') +} + +/** 导出System 共享工具文件。 */ +const exportTool = (toolId: string, toolName: string) => { + return getExportFile(`${toolName}.tool`, `${prefix}/${toolId}/export`) +} + +/** 测试工具配置是否可连接。 */ +const postToolTestConnection = (payload: ToolPayload) => { + return post(`${prefix}/test_connection`, payload) +} + +export default { + getToolPage, + exportTool, + deleteTool, + getToolDetail, + downloadSkillFile, + postTool, + postToolDebug, + postToolImport, + putUploadSkillFile, + postToolPylint, + postToolTestConnection, + putTool, +} diff --git a/ui/src/api/admin/workspace/chat-user.ts b/ui/src/api/admin/workspace/chat-user.ts new file mode 100644 index 00000000000..ea937bbbd85 --- /dev/null +++ b/ui/src/api/admin/workspace/chat-user.ts @@ -0,0 +1,63 @@ +import type { Ref } from 'vue' +import { Result } from '@/request/Result' +import { get, put } from '@/request/index' +import type { ChatUserGroupItem, ChatUserGroupUserItem, putUserGroupUserParams } from '@/api/type/workspaceChatUser' +import type { pageRequest, PageList } from '@/api/type/common' + +import useStore from '@/stores' +const prefix: any = { _value: '/workspace/' } +Object.defineProperty(prefix, 'value', { + get: function () { + const { user } = useStore() + return this._value + user.getWorkspaceId() + }, +}) +/** + * 获取用户组列表 + */ +const getUserGroupList: (resource: any, loading?: Ref) => Promise> = (resource, loading) => { + return get(`${prefix.value}/${resource.resource_type}/${resource.resource_id}/user_group`, undefined, loading) +} + +/** + * 修改用户组列表授权 + */ +const editUserGroupList: (resource: any, data: { user_group_id: string, is_auth: boolean }[], loading?: Ref) => Promise> = (resource, data, loading) => { + return put(`${prefix.value}/${resource.resource_type}/${resource.resource_id}/user_group`, data, undefined, loading) +} + +/** + * 获取用户组的用户列表 + */ +const getUserGroupUserList: ( + resource: any, + user_group_id: string, + page: pageRequest, + params?: any, + loading?: Ref, +) => Promise>> = (resource, user_group_id, page, params, loading) => { + return get( + `${prefix.value}/${resource.resource_type}/${resource.resource_id}/user_group_id/${user_group_id}/${page.current_page}/${page.page_size}`, + params, + loading, + ) +} + +/** + * 更新用户组的用户列表 + */ +const putUserGroupUser: ( + resource: any, + user_group_id: string, + data: putUserGroupUserParams[], + loading?: Ref, +) => Promise> = (resource, user_group_id, data, loading) => { + return put(`${prefix.value}/${resource.resource_type}/${resource.resource_id}/user_group_id/${user_group_id}`, data, undefined, loading) +} + +export default { + getUserGroupList, + editUserGroupList, + getUserGroupUserList, + putUserGroupUser +} diff --git a/ui/src/api/admin/workspace/model/model.ts b/ui/src/api/admin/workspace/model.ts similarity index 97% rename from ui/src/api/admin/workspace/model/model.ts rename to ui/src/api/admin/workspace/model.ts index 99150fda632..cbd6733dab9 100644 --- a/ui/src/api/admin/workspace/model/model.ts +++ b/ui/src/api/admin/workspace/model.ts @@ -1,4 +1,4 @@ -import { del, get, post, put } from '../../core/request' +import { del, get, post, put } from '../core/request' import type { Dict, DynamicFormField, ModelPayload, ModelItem } from '@/api/types' import { getWorkspaceId } from '@/utils/resource-context' diff --git a/ui/src/api/admin/workspace/related-resources.ts b/ui/src/api/admin/workspace/related-resources.ts index 51588dea1f8..3fd17703d7c 100644 --- a/ui/src/api/admin/workspace/related-resources.ts +++ b/ui/src/api/admin/workspace/related-resources.ts @@ -1,21 +1,22 @@ import { get } from '../core/request' +import { getWorkspaceId } from '@/utils/resource-context' import type { ParamsPage, ResponsePage } from '../core/types' import type { Dict, RelatedResource, ResourceType } from '@/api/types' /** 获取引用当前资源的资源。 */ -const getResourceDependents = (workspaceId: string, resource: ResourceType, resourceId: string, page: ParamsPage, query?: Dict) => { +const getUsedByResources = (resource: ResourceType, resourceId: string, page: ParamsPage, query?: Dict) => { return get>( - `/workspace/${workspaceId}/resource_mapping/${resource}/${resourceId}/${page.currentPage}/${page.pageSize}`, + `/workspace/${getWorkspaceId()}/resource_mapping/${resource}/${resourceId}/${page.currentPage}/${page.pageSize}`, query, ) } /** 获取当前资源依赖的资源。 */ -const getResourceDependencies = (workspaceId: string, resource: ResourceType, resourceId: string, page: ParamsPage, query?: Dict) => { +const getUsingResources = (resource: ResourceType, resourceId: string, page: ParamsPage, query?: Dict) => { return get>( - `/workspace/${workspaceId}/mapping_resource/${resource}/${resourceId}/${page.currentPage}/${page.pageSize}`, + `/workspace/${getWorkspaceId()}/mapping_resource/${resource}/${resourceId}/${page.currentPage}/${page.pageSize}`, query, ) } -export default { getResourceDependents, getResourceDependencies } +export default { getUsedByResources, getUsingResources } diff --git a/ui/src/api/types/model.ts b/ui/src/api/types/model.ts index e70745725bd..2e9a8a25c4c 100644 --- a/ui/src/api/types/model.ts +++ b/ui/src/api/types/model.ts @@ -23,6 +23,9 @@ export interface ModelProviderItem { } export interface ModelItem { + workspace_name?: string + resource_count?: number + update_time?: string create_time?: string id: string meta?: Record diff --git a/ui/src/api/types/tool.ts b/ui/src/api/types/tool.ts index 4c4f399d87e..c52539662a2 100644 --- a/ui/src/api/types/tool.ts +++ b/ui/src/api/types/tool.ts @@ -45,6 +45,8 @@ export interface ToolPylintIssue { } export interface ToolItem { + workspace_name?: string + resource_count?: number code?: string create_time?: string desc?: string | null diff --git a/ui/src/assets/iconfont.js b/ui/src/assets/iconfont.js index 462194ad8a3..220a24f8fd2 100644 --- a/ui/src/assets/iconfont.js +++ b/ui/src/assets/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_5200172='',(l=>{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var o,v,t,i,c,d=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!l.__iconfont__svg__cssinject__){l.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}o=function(){var a,h=document.createElement("div");h.innerHTML=l._iconfont_svg_string_5200172,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?d(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),o()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(t=o,i=l.document,c=!1,m(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,e())})}function e(){c||(c=!0,t())}function m(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(m,50)}e()}})(window); \ No newline at end of file +window._iconfont_svg_string_5200172='',(h=>{var a=(l=(l=document.getElementsByTagName("script"))[l.length-1]).getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var o,v,t,i,c,d=function(a,l){l.parentNode.insertBefore(a,l)};if(a&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}o=function(){var a,l=document.createElement("div");l.innerHTML=h._iconfont_svg_string_5200172,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(a=document.body).firstChild?d(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),o()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(t=o,i=h.document,c=!1,m(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,e())})}function e(){c||(c=!0,t())}function m(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(m,50)}e()}})(window); \ No newline at end of file diff --git a/ui/src/components.d.ts b/ui/src/components.d.ts index b900b6df53c..11245b90f1c 100644 --- a/ui/src/components.d.ts +++ b/ui/src/components.d.ts @@ -20,6 +20,7 @@ declare module 'vue' { MdEditor: typeof import('./components/global/markdown-editor/MdEditor.vue')['default'] MdEditorMagnify: typeof import('./components/global/markdown-editor/MdEditorMagnify.vue')['default'] MdPreview: typeof import('./components/global/markdown-editor/MdPreview.vue')['default'] + MkAction: typeof import('./components/global/mk-action/index.vue')['default'] MkCollapse: typeof import('./components/global/mk-collapse/index.vue')['default'] MkComplexSearch: typeof import('./components/global/mk-complex-search/index.vue')['default'] MkDialog: typeof import('./components/global/mk-dialog/index.vue')['default'] @@ -41,6 +42,7 @@ declare module 'vue' { MkTable: typeof import('./components/global/mk-table/index.vue')['default'] MkTableFilter: typeof import('./components/global/mk-table/MkTableFilter.vue')['default'] MkTableMoreDropdown: typeof import('./components/global/mk-table/MkTableMoreDropdown.vue')['default'] + MkTableOperationGroup: typeof import('./components/global/mk-table/MkTableOperationGroup.vue')['default'] MkTagGroup: typeof import('./components/global/mk-tag-group/index.vue')['default'] MkTooltip: typeof import('./components/global/mk-tooltip/index.vue')['default'] MkViewLayout: typeof import('./components/global/mk-view-layout/index.vue')['default'] @@ -62,6 +64,7 @@ declare global { const MdEditor: typeof import('./components/global/markdown-editor/MdEditor.vue')['default'] const MdEditorMagnify: typeof import('./components/global/markdown-editor/MdEditorMagnify.vue')['default'] const MdPreview: typeof import('./components/global/markdown-editor/MdPreview.vue')['default'] + const MkAction: typeof import('./components/global/mk-action/index.vue')['default'] const MkCollapse: typeof import('./components/global/mk-collapse/index.vue')['default'] const MkComplexSearch: typeof import('./components/global/mk-complex-search/index.vue')['default'] const MkDialog: typeof import('./components/global/mk-dialog/index.vue')['default'] @@ -83,6 +86,7 @@ declare global { const MkTable: typeof import('./components/global/mk-table/index.vue')['default'] const MkTableFilter: typeof import('./components/global/mk-table/MkTableFilter.vue')['default'] const MkTableMoreDropdown: typeof import('./components/global/mk-table/MkTableMoreDropdown.vue')['default'] + const MkTableOperationGroup: typeof import('./components/global/mk-table/MkTableOperationGroup.vue')['default'] const MkTagGroup: typeof import('./components/global/mk-tag-group/index.vue')['default'] const MkTooltip: typeof import('./components/global/mk-tooltip/index.vue')['default'] const MkViewLayout: typeof import('./components/global/mk-view-layout/index.vue')['default'] diff --git a/ui/src/components/COMPONENT_README.md b/ui/src/components/COMPONENT_README.md index 7f6135a6c9a..eb764b71b40 100644 --- a/ui/src/components/COMPONENT_README.md +++ b/ui/src/components/COMPONENT_README.md @@ -293,6 +293,23 @@ Dialog、Drawer、Popover、嵌套区域等其他大、小表格均禁止开启 `clearSelection()`。操作栏与 `MkViewLayout` 复用 `LayoutBatchFooter`,统一全选、半选、数量和 取消行为;它在主内容滚动区域内吸附于页面底部,不随表格内容滚出可视区域。 +### MkTableOperationGroup、MkAction + +表格操作列使用 `MkTableOperationGroup` 默认插槽声明操作,按模板顺序将前 `maxVisible` 个操作 +(默认 2)显示为图标按钮,其余自动进入 More;没有剩余操作时不显示 More。 +每个直接子组件代表一个操作,业务 Action 内使用 `MkAction` 作为展示入口。 +整个操作的显隐条件必须写在直接子组件的 `v-if` 上;不通过 `v-show` 或子组件内部条件控制计数。 +支持展开 `template` Fragment,忽略注释和文本;循环操作提供稳定且唯一的 key。 +禁用操作仍占位。开关等独立控件放在操作栏外,页面按实际入口数量设置操作列宽。 + +`MkAction` 位于 `global/mk-action/index.vue`,适用于表格和卡片,接收 `label`、SVG Symbol 名称 +`icon`、`disabled`、`divided`,发出 `click`。 +通过上下文自动选择图标按钮加 Tooltip 或图标加文字菜单项;独立使用默认显示按钮, +放入 `MkDropdownMenu` 时显示菜单项。点击阻止冒泡,`divided` 在按钮模式和自动 More 首项中不生效。 +业务 Action 保留自己的请求与浮层生命周期,不需要传递展示模式或维护操作配置数组。 +操作栏内部复用 `MkTableMoreDropdown` 并开启 `persistent`,菜单收起时保留业务 Action 与已打开浮层。 +操作跨越外露和 More 边界时会重新挂载,业务浮层打开期间不要重排入口或改变 `maxVisible`。 + ### MkTagGroup 始终渲染首个标签,更多标签折叠为 `+N`,悬浮展示剩余内容。 @@ -699,12 +716,14 @@ Skills 场景传入 `[TOOL_TYPE.SKILL]`,并通过 `title` 指定标题。两 ### RelatedResourcesDrawer 入口 `business/related-resources-drawer/index.vue`。传入完整 `api: typeof RelatedResourcesApi`, -通过 `open(resourceType, resource)` 传入含 `workspace_id` 的资源快照,用其所属工作空间查询。 +通过 `open(resourceType, resource)` 传入资源快照,`workspace_id` 可选。查询只传资源类型、资源 ID、分页与筛选; +Workspace API 内部使用 `getWorkspaceId()`,System 共享 API 不接收工作空间 ID。 `close()` 关闭,`closed` 在动画结束清理后触发。页面按范围选择真实 API,抽屉不拼接 System URL。 关系页签为“依赖 / 被依赖”,分别查询依赖的资源和引用当前资源的资源;模型、非工作流工具默认被依赖。 切换方向清空搜索、工作空间筛选与分页,资源名称当前仅展示,不提供跳转或相关回调。 -`showWorkspace` 由调用方决定,开启后查询工作空间选项并提供多选筛选,`workspace_ids` 当前传 JSON 字符串。 +抽屉内部通过 `isSystemSharedResource() && (auth.isEE || auth.isPE)` 判断是否显示工作空间列及筛选, +不接收 `showWorkspace` Prop。仅条件成立时查询工作空间选项并传递 `workspace_ids`(JSON 字符串)。 供应商图标由抽屉查询后传给内部 `ResourceIcon`,模型供应商标识来自关系记录的 `icon`。 当前查询没有请求版本校验,不保证旧响应在切换或关闭后被忽略。 diff --git a/ui/src/components/business/related-resources-drawer/index.vue b/ui/src/components/business/related-resources-drawer/index.vue index 42f610e8c4a..8762a449eaf 100644 --- a/ui/src/components/business/related-resources-drawer/index.vue +++ b/ui/src/components/business/related-resources-drawer/index.vue @@ -5,6 +5,8 @@ import type RelatedResourcesApi from '@/api/admin/workspace/related-resources' import ModelProviderApi from '@/api/admin/model-provider' import WorkspaceApi from '@/api/admin/system/workspace' import { RESOURCE_TYPE, TOOL_TYPE } from '@/api/enums' +import { useStore } from '@/stores' +import { isSystemSharedResource } from '@/utils/resource-context' import type { Dict, ModelProviderItem, OptionItem, RelatedResource, ResourceType } from '@/api/types' import ResourceIcon from './ResourceIcon.vue' import type { RelatedResourceTarget } from './types' @@ -12,11 +14,12 @@ import type { RelatedResourceTarget } from './types' defineOptions({ name: 'RelatedResourcesDrawer' }) const props = defineProps<{ api: typeof RelatedResourcesApi - showWorkspace?: boolean }>() const emit = defineEmits<{ closed: [] }>() /* 当前资源与展示配置 */ +const { auth } = useStore() +const showWorkspace = computed(() => isSystemSharedResource() && (auth.isEE || auth.isPE)) const visible = ref(false) const resourceType = ref(RESOURCE_TYPE.APPLICATION) const targetResource = ref() @@ -56,10 +59,10 @@ function loadResources() { const isDependency = relationDirection.value === 'dependency' const { resource_type, ...query } = searchQuery.value if (resource_type) query[isDependency ? 'target_type' : 'source_type'] = resource_type - if (selectedWorkspaceIds.value.length) query.workspace_ids = JSON.stringify(selectedWorkspaceIds.value) - const request = isDependency ? props.api.getResourceDependencies : props.api.getResourceDependents + if (showWorkspace.value && selectedWorkspaceIds.value.length) query.workspace_ids = JSON.stringify(selectedWorkspaceIds.value) + const request = isDependency ? props.api.getUsingResources : props.api.getUsedByResources loading.value = true - return request(targetResource.value.workspace_id, resourceType.value, targetResource.value.id, { ...pagination.value }, query) + return request(resourceType.value, targetResource.value.id, { ...pagination.value }, query) .then((page) => { relatedResources.value = page.records pagination.value.total = page.total @@ -106,7 +109,7 @@ function open(type: ResourceType, resource: RelatedResourceTarget) { providers.value = result }) } - if (props.showWorkspace) { + if (showWorkspace.value) { WorkspaceApi.getSystemWorkspaceList().then((workspaces) => { workspaceOptions.value = workspaces.flatMap(({ id, name }) => (id ? [{ value: id, label: name }] : [])) }) diff --git a/ui/src/components/business/related-resources-drawer/types.ts b/ui/src/components/business/related-resources-drawer/types.ts index 0ac0cb7e844..a9733126ba4 100644 --- a/ui/src/components/business/related-resources-drawer/types.ts +++ b/ui/src/components/business/related-resources-drawer/types.ts @@ -3,7 +3,7 @@ import type { ToolType } from '@/api/types' /** 打开抽屉时传入的资源快照。 */ export interface RelatedResourceTarget { id: string - workspace_id: string + workspace_id?: string name: string icon?: string | null type?: string | number diff --git a/ui/src/components/business/select-model/index.vue b/ui/src/components/business/select-model/index.vue index fbd6e8a36e6..5968ad14a4f 100644 --- a/ui/src/components/business/select-model/index.vue +++ b/ui/src/components/business/select-model/index.vue @@ -6,7 +6,7 @@ import type { ModelProviderItem, ModelItem } from '@/api/types' import { groupBy } from 'lodash' import ModelParamsDialog from './ModelParamsDialog.vue' import ButtonAddModel from '@/views/model/create-model/ButtonAddModel.vue' -import ModelApi from '@/api/admin/workspace/model/model' +import ModelApi from '@/api/admin/workspace/model' import SystemSharedModelApi from '@/api/admin/system/shared-resources/model' import { isWorkspaceResource, isSystemSharedResource } from '@/utils/resource-context' defineOptions({ name: 'SelectModel', inheritAttrs: false }) diff --git a/ui/src/components/global/mk-action/context.ts b/ui/src/components/global/mk-action/context.ts new file mode 100644 index 00000000000..40848f6407f --- /dev/null +++ b/ui/src/components/global/mk-action/context.ts @@ -0,0 +1,9 @@ +/** 表格操作与菜单共享的展示上下文。 */ +import type { InjectionKey, Ref } from 'vue' + +export interface ActionContext { + display: 'button' | 'menu' + first?: boolean +} + +export const actionContextKey: InjectionKey>> = Symbol('action-context') diff --git a/ui/src/components/global/mk-action/index.vue b/ui/src/components/global/mk-action/index.vue new file mode 100644 index 00000000000..278da3139a2 --- /dev/null +++ b/ui/src/components/global/mk-action/index.vue @@ -0,0 +1,29 @@ + + + diff --git a/ui/src/components/global/mk-dropdown/MkDropdownMenu.vue b/ui/src/components/global/mk-dropdown/MkDropdownMenu.vue index 22bbe0659a9..25389face1b 100644 --- a/ui/src/components/global/mk-dropdown/MkDropdownMenu.vue +++ b/ui/src/components/global/mk-dropdown/MkDropdownMenu.vue @@ -1,8 +1,13 @@ diff --git a/ui/src/components/global/mk-table/MkTableOperationGroup.vue b/ui/src/components/global/mk-table/MkTableOperationGroup.vue new file mode 100644 index 00000000000..850f084e332 --- /dev/null +++ b/ui/src/components/global/mk-table/MkTableOperationGroup.vue @@ -0,0 +1,60 @@ + diff --git a/ui/src/components/global/mk-table/index.vue b/ui/src/components/global/mk-table/index.vue index 02a8f1a0216..84b8913a0cc 100644 --- a/ui/src/components/global/mk-table/index.vue +++ b/ui/src/components/global/mk-table/index.vue @@ -196,5 +196,9 @@ defineExpose({ clearSelection, tableRef }) .el-table__column-resize-proxy { border-left: 2px solid var(--el-color-primary); } + + thead th { + border-bottom: none !important; + } } diff --git a/ui/src/components/mk-dynamics-form/Demo.vue b/ui/src/components/mk-dynamics-form/Demo.vue index 9123a3913d6..19165c11071 100644 --- a/ui/src/components/mk-dynamics-form/Demo.vue +++ b/ui/src/components/mk-dynamics-form/Demo.vue @@ -10,7 +10,7 @@ import { type FormField, type VisibilityFieldOption, } from '@/components/mk-dynamics-form' -import ModelApi from '@/api/admin/workspace/model/model' +import ModelApi from '@/api/admin/workspace/model' defineOptions({ name: 'MkDynamicsFormDemo' }) provide('getSelectModelList', ModelApi.getModelListWithShared) diff --git a/ui/src/router/ROUTE_README.md b/ui/src/router/ROUTE_README.md index 38d81eba105..8dc30996469 100644 --- a/ui/src/router/ROUTE_README.md +++ b/ui/src/router/ROUTE_README.md @@ -244,6 +244,8 @@ System 共享资源页面: /admin/system/share/tools ``` +`system-shared-tools` 渲染 `views/system/shared-resources/ToolSharedView.vue`,提供共享工具管理页面。 + 共享资源路由是 System 导航的一部分,继续在 `admin/system/index.ts` 的 `share` 子路由中维护。 已实现的共享资源页面放在 `views/system/shared-resources/`,由页面处理 System 范围的查询和操作, 可按需复用 Workspace 对应资源的卡片等展示组件。 @@ -354,3 +356,13 @@ Workspace 父路由为 `workspace-application-detail-layout`;System 详情实 高级设置的 `canAccess` 固定返回 false,避免工作流返回时重新进入画布;原有概览和简易设置的 返回权限条件保持不变。新增页面未添加权限判断。 `integration`、`chat-user`、`operation-log` 分别对应接入第三方、对话用户、操作日志占位页面。 + +### System 资源管理模型与工具 + +`/system/resource/models`、`/system/resource/tools` 分别渲染 +`views/system/resource-management/ModelResourceView.vue` 和 `ToolResourceView.vue`, +继承 `resourceScope: 'system-resource'`,保留原导航名称、顺序与权限。 +资源管理工具的全屏工作流使用 `system-resource-tool-workflow`,地址为 +`/system/resource/workspace/:workspaceId/tool/:toolId/workflow`,在 `admin/workflow/index.ts` +注册,复用 `ToolWorkflowView`。`workspaceId` 来自工具数据,用于画布辅助资源选择; +工具自身的查询、编辑、调试和发布使用 System API,返回 `system-resource-tools`。 diff --git a/ui/src/router/admin/system/index.ts b/ui/src/router/admin/system/index.ts index 6e1dc47432b..09271653d1d 100644 --- a/ui/src/router/admin/system/index.ts +++ b/ui/src/router/admin/system/index.ts @@ -145,7 +145,7 @@ export const systemRoutes: RouteRecordRaw = { redirect: { name: 'system-resource-applications' }, meta: { title: '资源管理', - icon: 'icon_setting', + icon: 'icon_project-settings-outlined', order: 20, resourceScope: 'system-resource', permission: () => { @@ -169,13 +169,13 @@ export const systemRoutes: RouteRecordRaw = { { path: 'tools', name: 'system-resource-tools', - component: () => import('@/views/system/SystemView.vue'), + component: () => import('@/views/system/resource-management/ToolResourceView.vue'), meta: { title: '工具', order: 30, permission: perm.tool.system.read, next: 'system-resource-models' }, }, { path: 'models', name: 'system-resource-models', - component: () => import('@/views/system/SystemView.vue'), + component: () => import('@/views/system/resource-management/ModelResourceView.vue'), meta: { title: '模型', order: 40, permission: perm.model.system.read, next: 'system-share' }, }, ], @@ -188,7 +188,7 @@ export const systemRoutes: RouteRecordRaw = { redirect: { name: 'system-shared-knowledge' }, meta: { title: '共享资源', - icon: 'icon_setting', + icon: 'icon_folder-share_outlined', order: 30, resourceScope: 'system-shared', permission: () => { @@ -201,19 +201,20 @@ export const systemRoutes: RouteRecordRaw = { path: 'knowledge', name: 'system-shared-knowledge', component: () => import('@/views/system/SystemView.vue'), - meta: { title: '知识库', order: 10, permission: perm.knowledge.share.jumpRead, next: 'system-shared-models' }, - }, - { - path: 'models', - name: 'system-shared-models', - component: () => import('@/views/system/shared-resources/SharedModelview.vue'), - meta: { title: '模型', order: 20, permission: perm.model.share.read, next: 'system-shared-tools' }, + meta: { title: '知识库', order: 10, permission: perm.knowledge.share.jumpRead, next: 'system-shared-tools' }, }, + { path: 'tools', name: 'system-shared-tools', - component: () => import('@/views/system/SystemView.vue'), - meta: { title: '工具', order: 30, permission: perm.tool.share.read, next: 'system-chat-management' }, + component: () => import('@/views/system/shared-resources/ToolSharedView.vue'), + meta: { title: '工具', order: 20, permission: perm.tool.share.read, next: 'system-shared-models' }, + }, + { + path: 'models', + name: 'system-shared-models', + component: () => import('@/views/system/shared-resources/ModelSharedview.vue'), + meta: { title: '模型', order: 30, permission: perm.model.share.read, next: 'system-chat-management' }, }, ], }, @@ -221,17 +222,14 @@ export const systemRoutes: RouteRecordRaw = { path: 'chat-management', name: 'system-chat-management', redirect: { name: 'system-chat-users' }, - meta: { title: '对话端管理', icon: 'icon_contacts_outlined', order: 40, - permission:() => - { - return ( - perm.system.chatUser.read() || - perm.system.chatUserGroup.read() || - perm.system.chatAuth.read() || - perm.system.portal.read() - ); - }, - next:'system-settings' + meta: { + title: '对话端管理', + icon: 'icon_contacts_outlined', + order: 40, + permission: () => { + return perm.system.chatUser.read() || perm.system.chatUserGroup.read() || perm.system.chatAuth.read() || perm.system.portal.read() + }, + next: 'system-settings', }, children: [ { @@ -250,10 +248,7 @@ export const systemRoutes: RouteRecordRaw = { path: 'authentication', name: 'system-chat-authentication', component: () => import('@/views/system/chat-management/authentication/AuthenticationView.vue'), - meta: { title: '对话用户认证', order: 30, - permission:perm.system.chatAuth.read, - next:'system-portal-setting' - }, + meta: { title: '对话用户认证', order: 30, permission: perm.system.chatAuth.read, next: 'system-portal-setting' }, }, { path: 'portal-setting', diff --git a/ui/src/router/admin/workflow/index.ts b/ui/src/router/admin/workflow/index.ts index 471eb6ba3ea..0434682e6df 100644 --- a/ui/src/router/admin/workflow/index.ts +++ b/ui/src/router/admin/workflow/index.ts @@ -14,6 +14,20 @@ export const workflowRoutes: RouteRecordRaw[] = [ component: () => import('@/views/workflow/tool/ToolWorkflowView.vue'), meta: { title: '工具工作流', resourceScope: 'workspace' }, }, + + { + path: '/system/resource//tool/:toolId/workflow', + name: 'system-resource-tool-workflow', + component: () => import('@/views/workflow/tool/ToolWorkflowView.vue'), + meta: { title: '工具工作流', resourceScope: 'system-resource' }, + }, + { + path: '/system/shared/tool/:toolId/workflow', + name: 'system-resource-tool-workflow', + component: () => import('@/views/workflow/tool/ToolWorkflowView.vue'), + meta: { title: '工具工作流', resourceScope: 'system-shared' }, + }, + { path: '/workspace/:workspaceId/knowledge/:knowledgeId/workflow', name: 'workflow-knowledge', diff --git a/ui/src/views/VIEW_README.md b/ui/src/views/VIEW_README.md index 633a3582c2e..543979c6b18 100644 --- a/ui/src/views/VIEW_README.md +++ b/ui/src/views/VIEW_README.md @@ -324,14 +324,17 @@ Action 放入 `views/system/shared-resources//action-dropdown/`。Act ```text src/views/system/shared-resources/ -├── SharedModelview.vue -└── model-card/action-dropdown/ - ├── index.ts - └── shared-model-info-action/ - ├── SharedModelInfoAction.vue - └── SharedModelInfoDrawer.vue +├── ModelSharedview.vue +└── ToolSharedView.vue ``` +`ToolSharedView` 使用 System 共享工具 API,复用 `ToolCard` 和编辑、启动参数、MCP 配置、 +导出、删除 Action。列表使用 `MkInfiniteScroll` 分页加载,支持类型、名称和创建者筛选; +创建与删除后重新查询,编辑和启停成功后替换对应卡片数据。 +`ButtonCreateTool` 可接收完整 `api`,默认使用 Workspace Tool API;`showWorkflow` 默认开启, +共享页关闭该入口,因为共享工作流画布尚未接入。共享页支持普通工具、Skills、MCP、数据源及 +导入创建,已有工作流工具只编辑基础信息。共享页不组合工作空间专用的移动、授权和触发器入口。 + System 用户页面按业务流程归拢操作入口和专属 Dialog。入口组件管理弹窗 Ref、打开动作并转发 `refresh`;列表页负责查询、批量选择和刷新策略,创建与编辑共用的 `UserFromDrawer` 仍由页面管理。 @@ -412,7 +415,10 @@ Dialog。新增或重命名文件时,应同步更新所有导入和页面功 | `system/settings/authentication/AuthenticationView.vue` | 系统登录及认证源配置页面 | | `system/settings/email/EmailSettingsView.vue` | 系统邮件 SMTP 服务配置页面 | | `system/operate-logs/OperateLogListView.vue` | 系统操作日志查询与清理页面 | -| `system/shared-resources/SharedModelview.vue` | System 共享模型查询与模型卡片页面 | +| `system/resource-management/ModelResourceView.vue` | System 模型分页、筛选、编辑、授权与删除 | +| `system/resource-management/ToolResourceView.vue` | System 工具分页、筛选、维护与工作流入口 | +| `system/shared-resources/ModelSharedview.vue` | System 共享模型查询与模型卡片页面 | +| `system/shared-resources/ToolSharedView.vue` | System 共享工具查询与工具维护页面 | | `trigger/TriggerView.vue` | 工作空间触发器查询、新建、编辑及单项和批量启停、删除 | | `tool/ToolView.vue` | 工作空间工具目录与工具卡片页面 | @@ -552,7 +558,7 @@ Dialog。新增或重命名文件时,应同步更新所有导入和页面功 ## 模型创建入口 `model/create-model/ButtonAddModel.vue` 和 `CreateModelDrawer.vue` 通过必填的 `api` 接收完整 -模型 API 对象。Workspace 模型页传入 `ModelApi`,System 共享模型页传入 `SystemSharedApi`; +模型 API 对象。Workspace 模型页传入 `ModelApi`,System 共享模型页传入 `SystemSharedModelApi`; 创建抽屉只调用传入的 API,不再自行判断资源范围。 `ButtonAddModel` 默认渲染主按钮,也支持通过默认作用域插槽的 `open()` 定制触发按钮, `SelectModel` 的下拉页脚使用该插槽复用同一创建流程。创建成功后保留基础资料刷新,再触发 @@ -615,10 +621,14 @@ Workspace 与 System 授权均使用该工作空间 ID,不读取路由工作 模型、知识库、应用、工具卡片的 `action-dropdown/` 分别提供 `RelatedResourcesModelAction`、`RelatedResourcesKnowledgeAction`、 `RelatedResourcesApplicationAction`、`RelatedResourcesToolAction`,在更多菜单展示“查看关联资源”。 -列表页面按对应资源的 `workspace.relateMap(resource.id)` 控制入口权限,共享知识库、工具和模型不展示。 +工作空间列表保留已有入口行为;System 共享模型页增加该 Action,不新增权限判断。 页面传入完整 `RelatedResourcesApi` 和当前资源。Action 点击后按需挂载 `RelatedResourcesDrawer`,传入对应 `RESOURCE_TYPE` 和包含 `workspace_id` 的资源快照; 工作空间 ID 来自资源数据,缺失时提示并停止打开,关闭后卸载抽屉。 +共享模型页只传入模型数据、文案和完整 System 共享关联资源 API,不传 `shared` 或工作空间显示标记。 +共享模型允许缺少 `workspace_id`;抽屉内部仅在 System 共享资源且为企业版或专业版时显示工作空间列与筛选。 +抽屉调用接口时不传工作空间 ID, +Workspace API 内部通过 `getWorkspaceId()` 读取当前路由工作空间。 模型及非工作流工具默认展示“引用此资源的资源”,其他资源默认展示“依赖的资源”。 资源名称仅展示文本,暂不支持打开目标资源。 @@ -787,3 +797,30 @@ application、tool、knowledge 的 WorkflowView 统一使用一个 `loading` 控 `operation-log/OperationLogView.vue` 分别承接接入第三方、对话用户、操作日志,目前仅展示占位内容, 标题及详情框架继续由 `ResourceDetailLayout` 提供。设置菜单按资源类型选择现有简易设置页面 或高级智能体全屏工作流,不另建高级设置 View。 + +## System 资源管理模型与工具 + +`system/resource-management/ModelResourceView.vue` 与 `ToolResourceView.vue` 使用 +`MkViewLayout`、`MkComplexSearch` 和 `MkTable` 展示跨工作空间资源。 +模型操作列通过 `MkTableOperationGroup` 按插槽顺序外露编辑与资源授权,其余操作自动进入 More; +参数设置的显隐条件直接写在 Action 上,业务 Action 复用 `MkAction` 适配按钮与菜单。 +模型 `action-dropdown` 中的编辑、删除、参数设置、授权和关联资源入口统一使用 `MkAction`。 +资源管理页复用 `EditModelAction`,传入 System API、供应商和禁用状态;该 Action 内聚编辑抽屉, +保存后发送 `refresh`。共享模型页继续组合相同业务 Action,在卡片菜单中自动显示为菜单项。 +资源管理删除保留页面专属确认、引用数量提示、加载状态和分页回退,通过 `MkAction` 展示, +不强行复用卡片的 `DeleteModelAction`。 +工具操作列继续使用 `MkTableMoreDropdown`。 +名称、类型、创建者(工具另含来源)筛选和工作空间表头筛选变化后回到第一页;分页大小变化由 +`MkTable` 重置页码。工作空间列及选项查询只在企业版开启,`workspace_ids` 沿用 JSON 数组字符串。 +页面使用 v3 的 System 权限方法保留迁移前已有的操作权限,不增加创建、导入、批量或复制入口。 +删除保留资源引用数量提示,删除末页最后一条后退回上一页。 + +模型复用编辑抽屉、参数设置、授权和关联资源 Action;工具复用各类型编辑表单、启停、启动参数、 +MCP 配置、授权、导出、触发器和执行记录。页面显式传入对应 System 业务 API。 +商店工具只修改名称,不开放模板代码编辑;编辑与启停合并返回数据,保留列表中的工作空间等展示字段。 +工具维护表单的 API 联合包含 System 资源对象,该范围没有 `postTool`,不通过伪造创建接口满足类型。 + +资源管理中的工作流进入 `system-resource-tool-workflow`,由 `ToolWorkflowView` 选择 System +工具、工作流和模型 API,并把工作流 API 传给发布历史与调试抽屉;返回资源管理工具列表。 +`TriggerToolAction` 和 `ResourceTriggerDialog` 透传可选 `toolApi`、`toolWorkflowApi`, +由 `TriggerFormDrawer` 查询当前资源的完整输入定义;未传时保持 Workspace 默认接口。 diff --git a/ui/src/views/home/components/HomeQuickCreate.vue b/ui/src/views/home/components/HomeQuickCreate.vue index ba7d7ea3264..0a957b4979a 100644 --- a/ui/src/views/home/components/HomeQuickCreate.vue +++ b/ui/src/views/home/components/HomeQuickCreate.vue @@ -3,7 +3,7 @@ import { onMounted, ref } from 'vue' import { useRouter } from 'vue-router' import type { ModelProviderItem } from '@/api/types' import ModelProviderApi from '@/api/admin/model-provider' -import ModelApi from '@/api/admin/workspace/model/model' +import ModelApi from '@/api/admin/workspace/model' import ButtonCreateApplication from '@/views/application/components/ButtonCreateApplication.vue' import ButtonCreateKnowledge from '@/views/knowledge/components/ButtonCreateKnowledge.vue' import ButtonCreateTool from '@/views/tool/components/ButtonCreateTool.vue' diff --git a/ui/src/views/knowledge/create-knowledge/components/KnowledgeBaseForm.vue b/ui/src/views/knowledge/create-knowledge/components/KnowledgeBaseForm.vue index 25bd0ef218c..f40592fafa3 100644 --- a/ui/src/views/knowledge/create-knowledge/components/KnowledgeBaseForm.vue +++ b/ui/src/views/knowledge/create-knowledge/components/KnowledgeBaseForm.vue @@ -3,7 +3,7 @@ import { onMounted, reactive, ref } from 'vue' import type { FormInstance, FormRules } from 'element-plus' import { MODEL_STATUS } from '@/api/enums' import type { KnowledgeCreatePayload, ModelItem, ModelProviderItem } from '@/api/types' -import ModelApi from '@/api/admin/workspace/model/model' +import ModelApi from '@/api/admin/workspace/model' import ModelProviderApi from '@/api/admin/model-provider' import SelectModel from '@/components/business/select-model/index.vue' diff --git a/ui/src/views/model/ModelView.vue b/ui/src/views/model/ModelView.vue index 327e64350e2..7501b641a97 100644 --- a/ui/src/views/model/ModelView.vue +++ b/ui/src/views/model/ModelView.vue @@ -3,7 +3,7 @@ import { computed, onMounted, ref } from 'vue' import type { Dict, ModelItem, ModelProviderItem, OptionItem } from '@/api/types' import { MODEL_TYPE_LABELS } from '@/constants' import CommonApi from '@/api/admin/workspace/common' -import ModelApi from '@/api/admin/workspace/model/model' +import ModelApi from '@/api/admin/workspace/model' import CommonSystemApi from '@/api/admin/system/common' import SharedApi from '@/api/admin/workspace/shared.ts' import ProviderApi from '@/api/admin/model-provider.ts' diff --git a/ui/src/views/model/create-model/ButtonAddModel.vue b/ui/src/views/model/create-model/ButtonAddModel.vue index 1211e10d33b..43a545cbf3b 100644 --- a/ui/src/views/model/create-model/ButtonAddModel.vue +++ b/ui/src/views/model/create-model/ButtonAddModel.vue @@ -1,6 +1,6 @@ diff --git a/ui/src/views/model/model-card/action-dropdown/RelatedResourcesModelAction.vue b/ui/src/views/model/model-card/action-dropdown/RelatedResourcesModelAction.vue index 1c3c90ca969..7d29dc6c10e 100644 --- a/ui/src/views/model/model-card/action-dropdown/RelatedResourcesModelAction.vue +++ b/ui/src/views/model/model-card/action-dropdown/RelatedResourcesModelAction.vue @@ -3,7 +3,6 @@ import { nextTick, ref, useTemplateRef } from 'vue' import type RelatedResourcesApi from '@/api/admin/workspace/related-resources' import type { ModelItem } from '@/api/types' import { RESOURCE_TYPE } from '@/api/enums' -import { MsgError } from '@/utils/message' import RelatedResourcesDrawer from '@/components/business/related-resources-drawer/index.vue' defineOptions({ name: 'RelatedResourcesModelAction' }) @@ -17,14 +16,8 @@ const props = defineProps<{ const drawerMounted = ref(false) const relatedResourcesDrawerRef = useTemplateRef>('relatedResourcesDrawerRef') function handleOpenRelatedResources() { - const workspaceId = props.model.workspace_id - if (!workspaceId) { - MsgError('模型缺少所属工作空间信息') - return - } - const model = { ...props.model, workspace_id: workspaceId } drawerMounted.value = true - return nextTick(() => relatedResourcesDrawerRef.value?.open(RESOURCE_TYPE.MODEL, model)) + return nextTick(() => relatedResourcesDrawerRef.value?.open(RESOURCE_TYPE.MODEL, props.model)) } function handleDrawerClosed() { drawerMounted.value = false @@ -33,9 +26,6 @@ function handleDrawerClosed() { diff --git a/ui/src/views/model/model-card/action-dropdown/edit-model-action/EditModelAction.vue b/ui/src/views/model/model-card/action-dropdown/edit-model-action/EditModelAction.vue index 6b89d0f6910..5a27fc0cc5f 100644 --- a/ui/src/views/model/model-card/action-dropdown/edit-model-action/EditModelAction.vue +++ b/ui/src/views/model/model-card/action-dropdown/edit-model-action/EditModelAction.vue @@ -1,13 +1,20 @@ diff --git a/ui/src/views/model/model-card/action-dropdown/edit-model-action/EditModelDrawer.vue b/ui/src/views/model/model-card/action-dropdown/edit-model-action/EditModelDrawer.vue index 3246b28692e..5881b5b05fe 100644 --- a/ui/src/views/model/model-card/action-dropdown/edit-model-action/EditModelDrawer.vue +++ b/ui/src/views/model/model-card/action-dropdown/edit-model-action/EditModelDrawer.vue @@ -1,6 +1,7 @@ diff --git a/ui/src/views/model/model-card/action-dropdown/param-setting-action/ParamSettingDrawer.vue b/ui/src/views/model/model-card/action-dropdown/param-setting-action/ParamSettingDrawer.vue index b559d0b3964..95a603cc970 100644 --- a/ui/src/views/model/model-card/action-dropdown/param-setting-action/ParamSettingDrawer.vue +++ b/ui/src/views/model/model-card/action-dropdown/param-setting-action/ParamSettingDrawer.vue @@ -1,6 +1,7 @@ + + diff --git a/ui/src/views/system/resource-management/ToolResourceView.vue b/ui/src/views/system/resource-management/ToolResourceView.vue new file mode 100644 index 00000000000..5230ef719e5 --- /dev/null +++ b/ui/src/views/system/resource-management/ToolResourceView.vue @@ -0,0 +1,314 @@ + + + diff --git a/ui/src/views/system/shared-resources/SharedModelview.vue b/ui/src/views/system/shared-resources/ModelSharedview.vue similarity index 74% rename from ui/src/views/system/shared-resources/SharedModelview.vue rename to ui/src/views/system/shared-resources/ModelSharedview.vue index 10c6cf5a6b6..c5bdccfb481 100644 --- a/ui/src/views/system/shared-resources/SharedModelview.vue +++ b/ui/src/views/system/shared-resources/ModelSharedview.vue @@ -2,13 +2,14 @@ import { computed, onMounted, ref } from 'vue' import type { Dict, ModelItem, ModelProviderItem, OptionItem } from '@/api/types' import { MODEL_TYPE_LABELS } from '@/constants' -import SystemSharedApi from '@/api/admin/system/shared-resources/model' +import SystemSharedModelApi from '@/api/admin/system/shared-resources/model' +import RelatedResourcesApi from '@/api/admin/system/shared-resources/related-resources' import CommonSystemApi from '@/api/admin/system/common' import ProviderApi from '@/api/admin/model-provider' import ModelCard from '@/views/model/model-card/ModelCard.vue' import ButtonAddModel from '@/views/model/create-model/ButtonAddModel.vue' import ModelProvider from '@/views/model/components/ModelProvider.vue' -import { DeleteModelAction, EditModelAction, ParamSettingAction } from '@/views/model/model-card/action-dropdown' +import { DeleteModelAction, EditModelAction, ParamSettingAction, RelatedResourcesModelAction } from '@/views/model/model-card/action-dropdown' const DEFAULT_MODEL_PROVIDER: ModelProviderItem = { icon: '', name: '全部模型', provider: 'all' } @@ -48,7 +49,7 @@ function loadModels() { const provider = currentProvider.value.provider const query = { ...modelQuery.value, ...(provider !== 'all' ? { provider } : {}) } - return SystemSharedApi.getModelList(query) + return SystemSharedModelApi.getModelList(query) .then((models) => { ModelItems.value = models }) @@ -85,18 +86,27 @@ onMounted(() => {

{{ currentProvider.name }}

- + +
diff --git a/ui/src/views/system/shared-resources/ToolSharedView.vue b/ui/src/views/system/shared-resources/ToolSharedView.vue new file mode 100644 index 00000000000..321875c7511 --- /dev/null +++ b/ui/src/views/system/shared-resources/ToolSharedView.vue @@ -0,0 +1,143 @@ + + + diff --git a/ui/src/views/tool/components/ButtonCreateTool.vue b/ui/src/views/tool/components/ButtonCreateTool.vue index 72fe8393e91..2c59888d857 100644 --- a/ui/src/views/tool/components/ButtonCreateTool.vue +++ b/ui/src/views/tool/components/ButtonCreateTool.vue @@ -2,6 +2,7 @@ import { computed, ref, useTemplateRef, type CSSProperties } from 'vue' import type { UploadFile, UploadInstance } from 'element-plus' import ToolApi from '@/api/admin/workspace/tool/tool' +import type SystemSharedToolApi from '@/api/admin/system/shared-resources/tool/tool' import { TOOL_TYPE } from '@/api/enums' import { useStore } from '@/stores' import { MsgSuccess } from '@/utils/message' @@ -17,13 +18,15 @@ const { auth } = useStore() const props = withDefaults( defineProps<{ + api?: typeof ToolApi | typeof SystemSharedToolApi + showWorkflow?: boolean folderId: string trigger?: 'click' | 'hover' | 'contextmenu' popperStyle?: CSSProperties popperClass?: string fitTriggerWidth?: boolean }>(), - { trigger: 'click', fitTriggerWidth: false }, + { api: () => ToolApi, showWorkflow: true, trigger: 'click', fitTriggerWidth: false }, ) const emit = defineEmits<{ refresh: [] }>() @@ -79,7 +82,8 @@ function handleOpenDataSourceForm() { const elUploadRef = ref() function handleImportCreate(file: UploadFile) { if (!file.raw) return - ToolApi.postToolImport(file.raw, props.folderId) + props.api + .postToolImport(file.raw, props.folderId) .then(() => { return auth.loadAuthBaseProfile().then(() => { MsgSuccess('导入成功') @@ -124,7 +128,7 @@ function handleRefresh() { 工具 - + 工作流 @@ -165,9 +169,9 @@ function handleRefresh() { - - - - - + + + + + diff --git a/ui/src/views/tool/tool-card/InitParamDialog.vue b/ui/src/views/tool/tool-card/InitParamDialog.vue index 5b4f78360db..107277cce1b 100644 --- a/ui/src/views/tool/tool-card/InitParamDialog.vue +++ b/ui/src/views/tool/tool-card/InitParamDialog.vue @@ -2,13 +2,15 @@ import { ref, useTemplateRef } from 'vue' import { cloneDeep } from 'lodash' import type ToolApi from '@/api/admin/workspace/tool/tool' +import type SystemResourceToolApi from '@/api/admin/system/resource-management/tool/tool' +import type SystemSharedToolApi from '@/api/admin/system/shared-resources/tool/tool' import type { Dict, ToolItem } from '@/api/types' import { MkDynamicsForm, type DynamicFormValue } from '@/components/mk-dynamics-form' import { MsgSuccess } from '@/utils/message' defineOptions({ name: 'InitParamDialog' }) -const props = defineProps<{ api: typeof ToolApi }>() +const props = defineProps<{ api: typeof ToolApi | typeof SystemSharedToolApi | typeof SystemResourceToolApi }>() const emit = defineEmits<{ closed: []; update: [tool: ToolItem] }>() diff --git a/ui/src/views/tool/tool-card/ToolCard.vue b/ui/src/views/tool/tool-card/ToolCard.vue index f70a60dbdf7..b32642e404c 100644 --- a/ui/src/views/tool/tool-card/ToolCard.vue +++ b/ui/src/views/tool/tool-card/ToolCard.vue @@ -1,5 +1,6 @@