diff --git a/ui/src/api/API_README.md b/ui/src/api/API_README.md index 5972ecf4519..c651e550554 100644 --- a/ui/src/api/API_README.md +++ b/ui/src/api/API_README.md @@ -97,12 +97,6 @@ Action、Drawer 或 Dialog。复用方直接使用 `typeof XxxApi` 约束完整 组装为 `{ id_list, folder_id }`,`putBatchDeleteKnowledge` 将 ID 数组组装为 `{ id_list }`, 分别使用 PUT 请求 `batch_move` 和 `batch_delete`。页面和 Action 负责类型判断及 loading。 -### 知识库文档标签 - -`workspace/knowledge/knowledge.ts` 的 `getKnowledgeTags(knowledgeIds)` 查询所选知识库的全部文档 -标签,返回按标签名称分组的 `KnowledgeTagGroup[]`。工作流页面将查询方法提供给文档标签检索 -节点,节点负责本地搜索和最多 100 项的展示。 - ## 枚举与类型组织 API 枚举与类型统一在 `src/api` 范围内管理,相关规则由本文档统一维护。 diff --git a/ui/src/assets/workflow/icon_document-extract.svg b/ui/src/assets/workflow/icon_document-extract.svg new file mode 100644 index 00000000000..9bec0be4a8f --- /dev/null +++ b/ui/src/assets/workflow/icon_document-extract.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/src/assets/workflow/icon_reranker.svg b/ui/src/assets/workflow/icon_reranker.svg new file mode 100644 index 00000000000..2e6d5aa4437 --- /dev/null +++ b/ui/src/assets/workflow/icon_reranker.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/src/assets/workflow/icon_search-document.svg b/ui/src/assets/workflow/icon_search-document.svg new file mode 100644 index 00000000000..7ab63ff9fd0 --- /dev/null +++ b/ui/src/assets/workflow/icon_search-document.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/src/views/tool/ToolView.vue b/ui/src/views/tool/ToolView.vue index 0d0126ee852..b082ed0e8df 100644 --- a/ui/src/views/tool/ToolView.vue +++ b/ui/src/views/tool/ToolView.vue @@ -6,7 +6,7 @@ import ToolApi from '@/api/admin/workspace/tool/tool' import SharedApi from '@/api/admin/workspace/shared' import ToolStoreApi from '@/api/admin/tool-store.ts' import type { Dict, FolderItem, OptionItem, ToolItem, ToolStoreResponse, ToolType } from '@/api/types' -import { RESOURCE_TYPE, TOOL_TYPE, TOOL_SCOPE } from '@/api/enums' +import { RESOURCE_TYPE, TOOL_TYPE } from '@/api/enums' import { TOOL_TYPE_OPTIONS, FOLDER_ENTRIES, FOLDER_ENTRY_ID } from '@/constants' import FolderTree from '@/components/business/folder-tree/index.vue' import MoveToDialog from '@/components/business/folder-tree/MoveToDialog.vue' @@ -69,7 +69,7 @@ const toolType = ref('') function loadToolsPage(pagination: { currentPage: number; pageSize: number }) { const request = isShared.value ? SharedApi : ToolApi - const folderId = isShared.value ? {} : { folder_id: currentFolder.value.id || FOLDER_ENTRY_ID.ALL, scope: TOOL_SCOPE.WORKSPACE } + const folderId = isShared.value ? {} : { folder_id: currentFolder.value.id || FOLDER_ENTRY_ID.ALL } return request.getToolPage(pagination, { ...toolQuery.value, tool_type: toolType.value, ...folderId }) } diff --git a/ui/src/workflow-canvas/WORKFLOW_README.md b/ui/src/workflow-canvas/WORKFLOW_README.md index cd5dbe36dfa..f6ecdc86f3b 100644 --- a/ui/src/workflow-canvas/WORKFLOW_README.md +++ b/ui/src/workflow-canvas/WORKFLOW_README.md @@ -196,8 +196,10 @@ AI 对话节点的提示词、历史记录、视觉理解和输出思考表单 标签条件使用 `MkFormList`,设置 `minRows` 为 `0`,保留允许删除到空列表的语义。文档标签检索通过 `NodeSearchScope` 复用知识库选择,保留知识库快照及缺少详情的关联 ID;标签条件使用 `MkFormList` 并设置 `minRows` 为 `0`,节点模型统一将新建及已有节点宽度设为 `455`。 -`ApplicationWorkflowView` 和 `ToolWorkflowView` 提供 `getKnowledgeTags` 与 `getRerankerModels` -注入接口,分别用于文档标签选项与重排模型查询;新增模型后直接重新查询,不读取已有模型列表缓存。 +`store/api/workspace/index.ts` 独立维护 `getAllTags(knowledgeIds)`,使用 `knowledge_ids[]` 查询 +所选知识库的全部文档标签,返回 `KnowledgeTagGroup[]`。文档标签选项通过工作流 Store 的 `force.getAllTags(knowledgeIds)` 查询,刷新时跳过已有标签缓存。 +`ApplicationWorkflowView` 和 `ToolWorkflowView` 提供重排模型查询注入接口;新增模型后直接重新查询, +不读取已有模型列表缓存。 标签请求只回写当前关联知识库的结果,过期或节点卸载后的响应不再修改节点。 固定字段写入统一使用直接赋值,例如 `model.properties.node_data = value`、 @@ -223,7 +225,9 @@ Vue `computed` 的 `set` 和原生 `Map.set()` 按各自 API 正常使用。 MkFormList 的排序、增删均以 `cloneDeep` 回写;MkTable 保留普通行对象引用,因此工作流节点 在 computed setter 中深拷贝新数组后写回 `model.properties.node_data`,并执行原有字段同步。 带 ID 的新行使用 `default-item` 工厂函数,每次添加生成独立 ID。条件分支等保留锚点 ID、 -固定末尾分支的特殊规则不能直接替换成普通列表移动。 +固定末尾分支的特殊规则不能直接替换成普通列表移动。判断器仅将 IF / ELSE IF 分支放入排序容器, +卡片悬停时显示排序手柄;ELSE 独立渲染并固定末尾。排序后按位置更新分支名称,保留分支 ID, +并同步刷新锚点顺序与连线。 ## View 接入约定 diff --git a/ui/src/workflow-canvas/icons/document-extract-node-icon.vue b/ui/src/workflow-canvas/icons/document-extract-node-icon.vue new file mode 100644 index 00000000000..496364542b0 --- /dev/null +++ b/ui/src/workflow-canvas/icons/document-extract-node-icon.vue @@ -0,0 +1,6 @@ + + diff --git a/ui/src/workflow-canvas/icons/reranker-node-icon.vue b/ui/src/workflow-canvas/icons/reranker-node-icon.vue new file mode 100644 index 00000000000..52e34f178bc --- /dev/null +++ b/ui/src/workflow-canvas/icons/reranker-node-icon.vue @@ -0,0 +1,6 @@ + + diff --git a/ui/src/workflow-canvas/icons/search-document-node-icon.vue b/ui/src/workflow-canvas/icons/search-document-node-icon.vue new file mode 100644 index 00000000000..3556a34efe9 --- /dev/null +++ b/ui/src/workflow-canvas/icons/search-document-node-icon.vue @@ -0,0 +1,6 @@ + + diff --git a/ui/src/workflow-canvas/nodes/condition-node/index.vue b/ui/src/workflow-canvas/nodes/condition-node/index.vue index bd8505a0057..3f07a536ab8 100644 --- a/ui/src/workflow-canvas/nodes/condition-node/index.vue +++ b/ui/src/workflow-canvas/nodes/condition-node/index.vue @@ -10,6 +10,7 @@ import NodeContainer from '@/workflow-canvas/core/node-container/index.vue' import { createAnchorGuard, handleNodeWheel } from '@/workflow-canvas/core/utils' import { compareList } from '@/workflow-canvas/config/constants' import { randomId } from '@/utils/common' +import { useSortable } from '@/utils/use-sortable' defineOptions({ name: 'WorkflowConditionNode' }) const getModel = inject('getModel') as () => BaseNodeModel @@ -52,6 +53,26 @@ const formData = computed(() => model.properties.node_data as { branch: BranchIt const conditionNodeFormRef = useTemplateRef('conditionNodeFormRef') const nodeCascaderRefs = useTemplateRef[]>('nodeCascaderRefs') +// 仅排序条件分支,ELSE 保留在独立容器和数据末尾。 +const branchListRef = useTemplateRef('branchListRef') +const elseBranch = computed(() => formData.value.branch.at(-1)) +const sortableBranches = computed({ + get: () => formData.value.branch.slice(0, -1), + set: (branches: BranchItem[]) => { + const reorderedBranches = cloneDeep([...branches, ...formData.value.branch.slice(-1)]) + reorderedBranches.slice(0, -1).forEach((branch, index) => { + branch.type = index === 0 ? 'IF' : `ELSE IF ${index}` + }) + formData.value.branch = reorderedBranches + refreshBranchAnchors() + }, +}) +useSortable(branchListRef, sortableBranches, () => ({ + disabled: sortableBranches.value.length < 2, + handle: '.condition-branch-handle', + draggable: '> .condition-branch', +})) + // 新分支插入 ELSE 之前,并保留稳定的锚点 ID。 function addBranch() { const branches = cloneDeep(formData.value.branch) @@ -134,11 +155,16 @@ onBeforeUnmount(() => { +