From 9e8354723f04fed803112a2be8cab085ac7235d8 Mon Sep 17 00:00:00 2001 From: wangdan-fit2cloud Date: Tue, 8 Sep 2026 10:15:09 +0800 Subject: [PATCH] feat: tool --- ui/src/api/API_README.md | 7 + ui/src/api/admin/workspace/tool/tool.ts | 11 +- ui/src/components/COMPONENT_README.md | 19 +- .../select-application-dialog/index.vue | 180 ++++++++++++++++ .../business/select-tool-dialog/index.vue | 182 ++++++++++++++++ ui/src/components/mk-form-list/index.vue | 6 +- .../router/admin/system/modules/knowledge.ts | 4 +- .../admin/workspace/modules/knowledge.ts | 4 +- ui/src/workflow-canvas/WORKFLOW_README.md | 30 ++- .../component/node-model-select/index.vue | 18 +- .../component/node-model-select/types.ts | 28 ++- ui/src/workflow-canvas/config/node-data.ts | 4 - .../resource-setting/McpSettingDialog.vue | 49 ++++- .../component/resource-setting/index.vue | 83 ++++++-- .../nodes/ai-chat-node/types.ts | 3 + .../workflow-canvas/nodes/mcp-node/index.vue | 3 +- ...ramSettingDialog.vue => SearchSetting.vue} | 34 ++- .../nodes/reranker-node/index.vue | 144 +++++-------- .../nodes/search-document-node/constant.ts | 9 + .../nodes/search-document-node/index.ts | 10 +- .../nodes/search-document-node/index.vue | 195 +++++------------- .../nodes/search-document-node/types.ts | 18 ++ .../store/api/workspace/index.ts | 2 +- ui/src/workflow-canvas/store/index.ts | 10 +- 24 files changed, 742 insertions(+), 311 deletions(-) create mode 100644 ui/src/components/business/select-application-dialog/index.vue create mode 100644 ui/src/components/business/select-tool-dialog/index.vue rename ui/src/workflow-canvas/nodes/reranker-node/component/{ParamSettingDialog.vue => SearchSetting.vue} (68%) create mode 100644 ui/src/workflow-canvas/nodes/search-document-node/constant.ts create mode 100644 ui/src/workflow-canvas/nodes/search-document-node/types.ts diff --git a/ui/src/api/API_README.md b/ui/src/api/API_README.md index c72d9f54e54..5972ecf4519 100644 --- a/ui/src/api/API_README.md +++ b/ui/src/api/API_README.md @@ -79,6 +79,13 @@ Action、Drawer 或 Dialog。复用方直接使用 `typeof XxxApi` 约束完整 额外维护逐方法接口,例如 `ModelActionApi`,也不要使用不断扩展的 `Pick`。 仅展示数据的组件不接收 API。 +### 工具列表查询 + +`workspace/tool/tool.ts` 的 `getAllTool(query)` 查询支持 `folder_id` 筛选的工作空间工具 +非分页列表,用于文件夹菜单和工具选择弹窗。`getToolListWithShared(query)` 请求 `tool/tool_list`, +将响应的 `tools` 与 `shared_tools` 合并为 `ToolItem[]`,用于包含已授权共享工具的选项查询; +按工具类型筛选时使用 `tool_type`。`workspace/shared.ts` 的 `getAllTool(query)` 仅查询共享工具。 + ### 知识库维护 `workspace/knowledge/knowledge.ts` 与 `workspace/shared.ts` 的 `getAllKnowledge(query)` diff --git a/ui/src/api/admin/workspace/tool/tool.ts b/ui/src/api/admin/workspace/tool/tool.ts index a41ed578ee6..2cd2d3803ce 100644 --- a/ui/src/api/admin/workspace/tool/tool.ts +++ b/ui/src/api/admin/workspace/tool/tool.ts @@ -8,11 +8,19 @@ const getPrefix = () => { return `/workspace/${workspaceId}/tool` } -/** 获取不分页的全部工具列表。 */ +/** 获取支持 folder_id 筛选的工作空间工具非分页列表。 */ const getAllTool = (query?: Dict) => { return get<{ tools: ToolItem[] }>(`${getPrefix()}`, query).then(({ tools }) => tools) } +/** 获取包含已授权共享工具的非分页列表。 */ +const getToolListWithShared = (query?: Dict) => { + return get<{ tools: ToolItem[]; shared_tools: ToolItem[] }>(`${getPrefix()}/tool_list`, query).then(({ tools, shared_tools }) => [ + ...tools, + ...shared_tools, + ]) +} + /** 获取工具分页列表。 */ const getToolPage = (page: ParamsPage, query?: Dict) => { return get>(`${getPrefix()}/${page.currentPage}/${page.pageSize}`, query) @@ -110,4 +118,5 @@ export default { putBatchMoveTools, putTool, getAllTool, + getToolListWithShared, } diff --git a/ui/src/components/COMPONENT_README.md b/ui/src/components/COMPONENT_README.md index b814188a652..af3afa8f426 100644 --- a/ui/src/components/COMPONENT_README.md +++ b/ui/src/components/COMPONENT_README.md @@ -40,6 +40,10 @@ src/components/ │ │ ├── MoveToDialog.vue │ │ ├── VirtualizedTree.vue │ │ └── types.ts +│ ├── select-application-dialog/ +│ │ └── index.vue # 已发布智能体选择 +│ ├── select-tool-dialog/ +│ │ └── index.vue # 工具与 Skills 按类型选择 │ ├── select-knowledge-dialog/ │ │ └── index.vue # 关联知识库选择、文件夹与共享资源查询 │ ├── select-model/ @@ -904,7 +908,8 @@ import MkSourceCard from '@/components/mk-source-card/index.vue' ### MkFormList 用于多个业务字段组成的动态表单行,负责重复行布局、添加、删除和可选排序,不管理业务字段、校验规则或 -选项请求。通过 `v-model` 传入行数据,`defaultItem` 创建新行,列表始终至少保留一行。默认插槽 +选项请求。通过 `v-model` 传入行数据,`defaultItem` 创建新行,`minRows` 默认值为 `1`,控制删除时保留的最小行数; +允许删除到空列表时传入 `:min-rows="0"`。组件不会自动补齐初始行。默认插槽 提供 `item`、`index`,业务组件在插槽中继续声明 `el-form-item`、字段路径和校验规则。 @@ -1173,6 +1178,18 @@ import FolderTree from '@/components/business/folder-tree/index.vue' 相同的 Embedding 模型。固定业务请求由 该组件负责,调用方维护最终关联 ID 和快照。 +### SelectApplicationDialog、SelectToolDialog + +手动导入 `business/select-application-dialog/index.vue` 和 `business/select-tool-dialog/index.vue`。 +两者沿用知识库选择弹窗的目录、名称搜索、三列卡片、悬停详情、跨目录选择和清空交互; +`open()` 接收已选资源对象数组,`submit` 返回深拷贝后的资源对象数组,兼容只有 ID 的旧数据。 +每次打开先重置临时状态;取消不提交,过期请求不写回。 + +智能体通过 `getAllApplication` 全量查询已发布资源,不展示共享目录。工具通过工作空间或共享 +`getAllTool` 全量查询,仅展示启用资源;`toolTypes` 默认包含自定义、工作流和内置工具, +Skills 场景传入 `[TOOL_TYPE.SKILL]`,并通过 `title` 指定标题。两者支持 `excludedIds`, +供调用方排除当前智能体或工具,避免直接自引用。弹窗不使用滚动分页。 + ### SelectModel 按供应商分组展示模型,通过 `v-model` 控制模型 ID,并在选择变化时触发 `change`。`options` 为 diff --git a/ui/src/components/business/select-application-dialog/index.vue b/ui/src/components/business/select-application-dialog/index.vue new file mode 100644 index 00000000000..97024fe991a --- /dev/null +++ b/ui/src/components/business/select-application-dialog/index.vue @@ -0,0 +1,180 @@ + + + diff --git a/ui/src/components/business/select-tool-dialog/index.vue b/ui/src/components/business/select-tool-dialog/index.vue new file mode 100644 index 00000000000..5bbabf74149 --- /dev/null +++ b/ui/src/components/business/select-tool-dialog/index.vue @@ -0,0 +1,182 @@ + + + diff --git a/ui/src/components/mk-form-list/index.vue b/ui/src/components/mk-form-list/index.vue index bd8527f4e8a..e4cecf238de 100644 --- a/ui/src/components/mk-form-list/index.vue +++ b/ui/src/components/mk-form-list/index.vue @@ -10,6 +10,7 @@ const props = withDefaults( addText?: string defaultItem: T | (() => T) firstRowHasLabel?: boolean + minRows?: number showAddButton?: boolean sortable?: boolean itemKey?: keyof T | ((item: T) => string | number) @@ -17,6 +18,7 @@ const props = withDefaults( { addText: '添加', firstRowHasLabel: true, + minRows: 1, showAddButton: true, sortable: false, }, @@ -60,7 +62,7 @@ function addRow() { } function removeRow(index: number) { - if (formRows.value.length === 1) return + if (formRows.value.length <= props.minRows) return const removedItem = formRows.value[index] as T formRows.value = cloneDeep(formRows.value.filter((_, rowIndex) => rowIndex !== index)) emit('remove', removedItem, index) @@ -82,7 +84,7 @@ function removeRow(index: number) { - + diff --git a/ui/src/router/admin/system/modules/knowledge.ts b/ui/src/router/admin/system/modules/knowledge.ts index 75eb8856717..0c3cce0a6bf 100644 --- a/ui/src/router/admin/system/modules/knowledge.ts +++ b/ui/src/router/admin/system/modules/knowledge.ts @@ -5,13 +5,13 @@ export const systemKnowledgeRoutes: RouteRecordRaw[] = [ { path: 'knowledge/:knowledgeId', name: 'system-knowledge-detail', - component: () => import('@/views/knowledge/KnowledgeDetailView.vue'), + component: () => import('@/views/knowledge-detail/KnowledgeDetailView.vue'), meta: { title: '知识库详情', hidden: true }, }, { path: 'knowledge/:knowledgeId/document/:documentId', name: 'system-knowledge-document-detail', - component: () => import('@/views/knowledge/DocumentDetailView.vue'), + component: () => import('@/views/knowledge-detail/DocumentDetailView.vue'), meta: { title: '文档详情', hidden: true }, }, ] diff --git a/ui/src/router/admin/workspace/modules/knowledge.ts b/ui/src/router/admin/workspace/modules/knowledge.ts index b356c9b3976..82e06e35795 100644 --- a/ui/src/router/admin/workspace/modules/knowledge.ts +++ b/ui/src/router/admin/workspace/modules/knowledge.ts @@ -11,13 +11,13 @@ export const knowledgeRoutes: RouteRecordRaw[] = [ { path: ':knowledgeId', name: 'workspace-knowledge-detail', - component: () => import('@/views/knowledge/KnowledgeDetailView.vue'), + component: () => import('@/views/knowledge-detail/KnowledgeDetailView.vue'), meta: { title: '知识库详情', hidden: true }, }, { path: ':knowledgeId/document/:documentId', name: 'workspace-knowledge-document-detail', - component: () => import('@/views/knowledge/DocumentDetailView.vue'), + component: () => import('@/views/knowledge-detail/DocumentDetailView.vue'), meta: { title: '文档详情', hidden: true }, }, ], diff --git a/ui/src/workflow-canvas/WORKFLOW_README.md b/ui/src/workflow-canvas/WORKFLOW_README.md index 3f82e5eed2d..cd5dbe36dfa 100644 --- a/ui/src/workflow-canvas/WORKFLOW_README.md +++ b/ui/src/workflow-canvas/WORKFLOW_README.md @@ -88,6 +88,11 @@ LogicFlow 的节点拖拽;仅拦截 `mousedown` 无法隔离当前版本的 Po 搜索和资源列表;`index.vue` 只负责 Tabs 和事件汇总。`component/AddNode.vue` 负责画布右上角的 悬浮入口、菜单显隐,以及点击添加和拖拽添加事件;画布入口传入 `workflowMode` 并完成节点创建。 +工作流 Store 的 `getToolListWithShared` 用于包含已授权共享工具的选项查询,MCP 节点使用 +`tool_type: 'MCP'` 筛选;AI 对话的 `McpSettingDialog` 每次打开通过 +`store.force.getToolListWithShared` 刷新同类选项,关闭或重新打开后忽略旧响应,通过 `loaded` +将工具详情提供给资源区回显;取消不修改已选配置。文件夹菜单继续使用 `getAllTool`,不复用这一路查询。 + ### `nodes/` 每个已实现节点使用一个目录,目录中包含 LogicFlow 注册文件 `index.ts` 和节点视图 @@ -109,7 +114,7 @@ Vue Teleport 节点继承页面上下文;画布核心不负责模型参数接 只用于解析当前使用的模型,不覆盖节点保存的自定义配置。 AI 对话、意图识别、问题优化、参数提取、图片理解、视频理解、图片生成、文生视频、图生视频、 -语音转文本和文本转语音这 11 个节点统一使用 `component/node-model-select/index.vue` 的 +语音转文本、文本转语音和多路召回这 12 个节点统一使用 `component/node-model-select/index.vue` 的 `NodeModelSelect`。组件接收 `nodeModel`、只读 `formData`、`modelType`、`label`、`options` 和 `providerOptions`;默认字段为 `model_id_type`、`model_id`、`model_id_reference` 与 `model_params_setting`,语音节点通过类型化的 `fields` 映射 STT/TTS 字段。组件通过 `update` @@ -118,15 +123,19 @@ AI 对话、意图识别、问题优化、参数提取、图片理解、视频 默认来源检查保存后的默认模型,自定义来源检查模型 ID,引用来源检查必填并调用 `NodeCascader.validate()` 检查引用有效性。切换来源清空引用与旧校验,保留自定义模型和参数。 来源及模型下拉由组件统一维护锚点保护;参数按钮与默认参数加载继续复用 `SelectModel`。 -多路召回、基本信息中的语音设置和长期记忆保持各自实现,不接入该组件。 +多路召回通过 `fields` 映射 `reranker_model_id*`,不提供参数字段,并设置 `canEditParams` 为 +`false`,仅保留独立的检索参数弹窗;模型校验统一遵循上述规则,包括默认模型必填。 +`canEditParams` 默认为 `true`,仅配置参数字段时启用;`canAdd` 默认为 `false`,多路召回开启后 +通过 `refresh` 重新查询重排模型,保留添加模型能力。 +基本信息中的语音设置和长期记忆保持各自实现,不接入该组件。 -知识库检索的范围区块使用 `component/node-search-scope/index.vue` 的 `NodeSearchScope`。 +知识库检索与文档标签检索的范围区块使用 `component/node-search-scope/index.vue` 的 `NodeSearchScope`。 组件通过 `formData` 接收 `NodeSearchScopeData`、通过 `selectedKnowledge` 接收包含 ID 回退的 知识库快照,`nodeModel` 仅用于变量选择和浮层锚点保护。`update` 提交范围字段的局部变更, `update:knowledge` 提交选择或移除后的知识库列表;节点保留快照、关联 ID 和不可见关联的清理逻辑。 组件复用 `SelectKnowledgeDialog`,保留相同 Embedding 模型约束。切换范围保留配置,切换知识库/ 文档列表清空引用;引用的必填与有效性校验通过组件内的表单项加入节点外层表单,自定义范围不校验 -隐藏引用,也不新增知识库必填限制。文档标签检索后续可以复用该组件,标签加载与过滤仍归节点。 +隐藏引用,也不新增知识库必填限制。文档标签的加载与过滤仍归文档标签检索节点。 节点自身的表单、状态和专属校验留在节点目录;多个节点共享且属于画布基础协议的能力才上移到 `core`。节点应复用 `core/node-container/index.vue`,需要选择上游节点字段时复用 @@ -167,6 +176,10 @@ AI 对话、意图识别、问题优化、参数提取、图片理解、视频 AI 对话节点的 `component/resource-setting` 仅渲染技能卡片内的 MCP、工具、Skills 和智能体分组, 各分组直接在入口使用 `MkCollapse`,不再拆分普通列表子组件,仅选择弹窗独立封装。 +工具和 Skills 的添加入口复用 `SelectToolDialog`,智能体入口使用 `SelectApplicationDialog`; +Skills 仅查询 SKILL 类型,选择器排除当前路由对应的智能体或工具。确认后同时更新执行用的 +ID 数组及 `tool_list`、`skill_tool_list`、`application_list` 回显快照;移除时同步清理快照, +取消不更新节点,旧数据缺少快照时继续显示 ID。 标题、输出执行过程开关与 `mk-white-card` 由节点入口维护。组件通过 `setting` 读取资源配置、通过 `update` 提交局部变更;资源选项由 Props 传入,未接入数据源时默认为空数组, 已关联但缺少详情的资源保留 ID 回退展示。 @@ -177,9 +190,12 @@ AI 对话节点的提示词、历史记录、视觉理解和输出思考表单 节点的正向与负向提示词同样使用 `MdEditorMagnify`,必填字段由所在节点表单统一校验。 文档内容提取、多路召回和文档标签检索节点使用统一的节点标题、`mk-gray-card`、表单必填标记与 -`MkIcon`。多路召回的独立参数弹窗位于节点 `component/`,复用 `MkDialog` 和 `MkSlider`; -重排内容与标签条件允许删除到空列表,保留原有列表语义。文档标签检索复用 -`SelectKnowledgeDialog`,保留知识库快照及缺少详情的关联 ID。 +`MkIcon`。多路召回的参数设置按钮与弹窗封装在节点 `component/SearchSetting.vue`,通过 +`v-model` 接收检索参数,保存时深拷贝回写,取消不修改节点;弹窗复用 `MkDialog` 和 `MkSlider`。 +重排内容使用 `MkFormList` 并隐藏内置添加按钮,由标题栏按钮深拷贝新增空引用,保留至少一行; +标签条件使用 `MkFormList`,设置 `minRows` 为 `0`,保留允许删除到空列表的语义。文档标签检索通过 +`NodeSearchScope` 复用知识库选择,保留知识库快照及缺少详情的关联 ID;标签条件使用 +`MkFormList` 并设置 `minRows` 为 `0`,节点模型统一将新建及已有节点宽度设为 `455`。 `ApplicationWorkflowView` 和 `ToolWorkflowView` 提供 `getKnowledgeTags` 与 `getRerankerModels` 注入接口,分别用于文档标签选项与重排模型查询;新增模型后直接重新查询,不读取已有模型列表缓存。 标签请求只回写当前关联知识库的结果,过期或节点卸载后的响应不再修改节点。 diff --git a/ui/src/workflow-canvas/component/node-model-select/index.vue b/ui/src/workflow-canvas/component/node-model-select/index.vue index bac85fad812..818f4407a86 100644 --- a/ui/src/workflow-canvas/component/node-model-select/index.vue +++ b/ui/src/workflow-canvas/component/node-model-select/index.vue @@ -15,16 +15,20 @@ const props = withDefaults( nodeModel: WorkflowNodeModel formData: NodeModelData fields?: NodeModelFields + canEditParams?: boolean + canAdd?: boolean modelType: DefaultModelType label: string options: ModelItem[] providerOptions: ModelProviderItem[] }>(), { + canEditParams: true, + canAdd: false, fields: () => ({ source: 'model_id_type', id: 'model_id', reference: 'model_id_reference', params: 'model_params_setting' }), }, ) -const emit = defineEmits<{ update: [patch: Partial] }>() +const emit = defineEmits<{ update: [patch: Partial]; refresh: [] }>() const formItemRef = useTemplateRef('formItemRef') const modelCascaderRef = useTemplateRef>('modelCascaderRef') @@ -40,6 +44,10 @@ function changeSource(value: NodeModelSource) { nextTick(() => formItemRef.value?.clearValidate()) } +function updateModelParams(params: Record) { + if (props.canEditParams && props.fields.params) emit('update', { [props.fields.params]: params }) +} + // 注册到节点的外层表单,引用有效性失败也由同一个表单项展示。 async function validateModel() { if (source.value === 'reference') { @@ -96,13 +104,15 @@ onBeforeUnmount(() => anchorGuard.reset()) diff --git a/ui/src/workflow-canvas/component/node-model-select/types.ts b/ui/src/workflow-canvas/component/node-model-select/types.ts index b16bcfa7365..65bd6092b06 100644 --- a/ui/src/workflow-canvas/component/node-model-select/types.ts +++ b/ui/src/workflow-canvas/component/node-model-select/types.ts @@ -10,15 +10,25 @@ export interface NodeModelData { tts_model_id_type?: NodeModelSource tts_model_id?: string tts_model_id_reference?: string[] - model_params_setting: Record + reranker_model_id_type?: NodeModelSource + reranker_model_id?: string + reranker_model_id_reference?: string[] + model_params_setting?: Record } // 映射仅支持现有节点协议,同一组来源、模型和引用字段保持对应。 -export type NodeModelFields = { - [Prefix in '' | 'stt_' | 'tts_']: { - source: `${Prefix}model_id_type` - id: `${Prefix}model_id` - reference: `${Prefix}model_id_reference` - params: 'model_params_setting' - } -}['' | 'stt_' | 'tts_'] +export type NodeModelFields = + | { + [Prefix in '' | 'stt_' | 'tts_']: { + source: `${Prefix}model_id_type` + id: `${Prefix}model_id` + reference: `${Prefix}model_id_reference` + params: 'model_params_setting' + } + }['' | 'stt_' | 'tts_'] + | { + source: 'reranker_model_id_type' + id: 'reranker_model_id' + reference: 'reranker_model_id_reference' + params?: never + } diff --git a/ui/src/workflow-canvas/config/node-data.ts b/ui/src/workflow-canvas/config/node-data.ts index 3892475a2e1..2090a90604b 100644 --- a/ui/src/workflow-canvas/config/node-data.ts +++ b/ui/src/workflow-canvas/config/node-data.ts @@ -300,9 +300,7 @@ export const searchDocumentNode = { type: WorkflowNodeType.SearchDocument, text: '从设定的检索范围中,根据文档标签检索出满足条件的文档', label: '文档标签检索', - height: 355, properties: { - width: 600, stepName: '文档标签检索', config: { fields: [ @@ -318,7 +316,6 @@ export const documentExtractNode = { type: WorkflowNodeType.DocumentExtractNode, text: '解析输入文档,输出结构化文档内容', label: '文档内容提取', - height: 252, properties: { stepName: '文档内容提取', config: { @@ -335,7 +332,6 @@ export const rerankerNode = { type: WorkflowNodeType.RerankerNode, text: '使用重排模型对多个知识库的检索结果进行二次召回', label: '多路召回', - height: 252, properties: { stepName: '多路召回', config: { diff --git a/ui/src/workflow-canvas/nodes/ai-chat-node/component/resource-setting/McpSettingDialog.vue b/ui/src/workflow-canvas/nodes/ai-chat-node/component/resource-setting/McpSettingDialog.vue index b1aa706fed0..04f4e7d1fc0 100644 --- a/ui/src/workflow-canvas/nodes/ai-chat-node/component/resource-setting/McpSettingDialog.vue +++ b/ui/src/workflow-canvas/nodes/ai-chat-node/component/resource-setting/McpSettingDialog.vue @@ -1,14 +1,18 @@ diff --git a/ui/src/workflow-canvas/nodes/ai-chat-node/types.ts b/ui/src/workflow-canvas/nodes/ai-chat-node/types.ts index a9029ebc494..107485d0519 100644 --- a/ui/src/workflow-canvas/nodes/ai-chat-node/types.ts +++ b/ui/src/workflow-canvas/nodes/ai-chat-node/types.ts @@ -40,6 +40,9 @@ export interface McpSetting { } export interface ResourceSetting extends McpSetting { + application_list?: (Partial & { id: string })[] + tool_list?: (Partial & { id: string })[] + skill_tool_list?: (Partial & { id: string })[] application_ids: string[] mcp_output_enable: boolean skill_tool_ids: string[] diff --git a/ui/src/workflow-canvas/nodes/mcp-node/index.vue b/ui/src/workflow-canvas/nodes/mcp-node/index.vue index 6252d7d3258..e0a32d9b6ea 100644 --- a/ui/src/workflow-canvas/nodes/mcp-node/index.vue +++ b/ui/src/workflow-canvas/nodes/mcp-node/index.vue @@ -94,6 +94,7 @@