Skip to content

Commit 0d540ee

Browse files
feat: node
1 parent 2df6f5b commit 0d540ee

16 files changed

Lines changed: 199 additions & 95 deletions

File tree

‎ui/src/api/API_README.md‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,6 @@ Action、Drawer 或 Dialog。复用方直接使用 `typeof XxxApi` 约束完整
9797
组装为 `{ id_list, folder_id }`,`putBatchDeleteKnowledge` 将 ID 数组组装为 `{ id_list }`,
9898
分别使用 PUT 请求 `batch_move` 和 `batch_delete`。页面和 Action 负责类型判断及 loading。
9999

100-
### 知识库文档标签
101-
102-
`workspace/knowledge/knowledge.ts` 的 `getKnowledgeTags(knowledgeIds)` 查询所选知识库的全部文档
103-
标签,返回按标签名称分组的 `KnowledgeTagGroup[]`。工作流页面将查询方法提供给文档标签检索
104-
节点,节点负责本地搜索和最多 100 项的展示。
105-
106100
## 枚举与类型组织
107101

108102
API 枚举与类型统一在 `src/api` 范围内管理,相关规则由本文档统一维护。
Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 4 additions & 0 deletions
Loading

‎ui/src/views/tool/ToolView.vue‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import ToolApi from '@/api/admin/workspace/tool/tool'
66
import SharedApi from '@/api/admin/workspace/shared'
77
import ToolStoreApi from '@/api/admin/tool-store.ts'
88
import type { Dict, FolderItem, OptionItem, ToolItem, ToolStoreResponse, ToolType } from '@/api/types'
9-
import { RESOURCE_TYPE, TOOL_TYPE, TOOL_SCOPE } from '@/api/enums'
9+
import { RESOURCE_TYPE, TOOL_TYPE } from '@/api/enums'
1010
import { TOOL_TYPE_OPTIONS, FOLDER_ENTRIES, FOLDER_ENTRY_ID } from '@/constants'
1111
import FolderTree from '@/components/business/folder-tree/index.vue'
1212
import MoveToDialog from '@/components/business/folder-tree/MoveToDialog.vue'
@@ -69,7 +69,7 @@ const toolType = ref<ToolType | ''>('')
6969
7070
function loadToolsPage(pagination: { currentPage: number; pageSize: number }) {
7171
const request = isShared.value ? SharedApi : ToolApi
72-
const folderId = isShared.value ? {} : { folder_id: currentFolder.value.id || FOLDER_ENTRY_ID.ALL, scope: TOOL_SCOPE.WORKSPACE }
72+
const folderId = isShared.value ? {} : { folder_id: currentFolder.value.id || FOLDER_ENTRY_ID.ALL }
7373
return request.getToolPage(pagination, { ...toolQuery.value, tool_type: toolType.value, ...folderId })
7474
}
7575

‎ui/src/workflow-canvas/WORKFLOW_README.md‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,10 @@ AI 对话节点的提示词、历史记录、视觉理解和输出思考表单
196196
标签条件使用 `MkFormList`,设置 `minRows` 为 `0`,保留允许删除到空列表的语义。文档标签检索通过
197197
`NodeSearchScope` 复用知识库选择,保留知识库快照及缺少详情的关联 ID;标签条件使用
198198
`MkFormList` 并设置 `minRows` 为 `0`,节点模型统一将新建及已有节点宽度设为 `455`。
199-
`ApplicationWorkflowView` 和 `ToolWorkflowView` 提供 `getKnowledgeTags` 与 `getRerankerModels`
200-
注入接口,分别用于文档标签选项与重排模型查询;新增模型后直接重新查询,不读取已有模型列表缓存。
199+
`store/api/workspace/index.ts` 独立维护 `getAllTags(knowledgeIds)`,使用 `knowledge_ids[]` 查询
200+
所选知识库的全部文档标签,返回 `KnowledgeTagGroup[]`。文档标签选项通过工作流 Store 的 `force.getAllTags(knowledgeIds)` 查询,刷新时跳过已有标签缓存。
201+
`ApplicationWorkflowView` 和 `ToolWorkflowView` 提供重排模型查询注入接口;新增模型后直接重新查询,
202+
不读取已有模型列表缓存。
201203
标签请求只回写当前关联知识库的结果,过期或节点卸载后的响应不再修改节点。
202204

203205
固定字段写入统一使用直接赋值,例如 `model.properties.node_data = value`、
@@ -223,7 +225,9 @@ Vue `computed` 的 `set` 和原生 `Map.set()` 按各自 API 正常使用。
223225
MkFormList 的排序、增删均以 `cloneDeep` 回写;MkTable 保留普通行对象引用,因此工作流节点
224226
在 computed setter 中深拷贝新数组后写回 `model.properties.node_data`,并执行原有字段同步。
225227
带 ID 的新行使用 `default-item` 工厂函数,每次添加生成独立 ID。条件分支等保留锚点 ID、
226-
固定末尾分支的特殊规则不能直接替换成普通列表移动。
228+
固定末尾分支的特殊规则不能直接替换成普通列表移动。判断器仅将 IF / ELSE IF 分支放入排序容器,
229+
卡片悬停时显示排序手柄;ELSE 独立渲染并固定末尾。排序后按位置更新分支名称,保留分支 ID,
230+
并同步刷新锚点顺序与连线。
227231

228232
## View 接入约定
229233

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<template>
2+
<el-avatar shape="square">
3+
<img src="@/assets/workflow/icon_document-extract.svg" style="width: 64%" alt="" />
4+
</el-avatar>
5+
</template>
6+
<script setup lang="ts"></script>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<template>
2+
<el-avatar shape="square" class="bg-purple!">
3+
<img src="@/assets/workflow/icon_reranker.svg" style="width: 64%" alt="" />
4+
</el-avatar>
5+
</template>
6+
<script setup lang="ts"></script>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<template>
2+
<el-avatar shape="square">
3+
<img src="@/assets/workflow/icon_search-document.svg" style="width: 64%" alt="" />
4+
</el-avatar>
5+
</template>
6+
<script setup lang="ts"></script>

‎ui/src/workflow-canvas/nodes/condition-node/index.vue‎

Lines changed: 77 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import NodeContainer from '@/workflow-canvas/core/node-container/index.vue'
1010
import { createAnchorGuard, handleNodeWheel } from '@/workflow-canvas/core/utils'
1111
import { compareList } from '@/workflow-canvas/config/constants'
1212
import { randomId } from '@/utils/common'
13+
import { useSortable } from '@/utils/use-sortable'
1314
1415
defineOptions({ name: 'WorkflowConditionNode' })
1516
const getModel = inject('getModel') as () => BaseNodeModel
@@ -52,6 +53,26 @@ const formData = computed(() => model.properties.node_data as { branch: BranchIt
5253
const conditionNodeFormRef = useTemplateRef<FormInstance>('conditionNodeFormRef')
5354
const nodeCascaderRefs = useTemplateRef<InstanceType<typeof NodeCascader>[]>('nodeCascaderRefs')
5455
56+
// 仅排序条件分支,ELSE 保留在独立容器和数据末尾。
57+
const branchListRef = useTemplateRef<HTMLElement>('branchListRef')
58+
const elseBranch = computed(() => formData.value.branch.at(-1))
59+
const sortableBranches = computed({
60+
get: () => formData.value.branch.slice(0, -1),
61+
set: (branches: BranchItem[]) => {
62+
const reorderedBranches = cloneDeep([...branches, ...formData.value.branch.slice(-1)])
63+
reorderedBranches.slice(0, -1).forEach((branch, index) => {
64+
branch.type = index === 0 ? 'IF' : `ELSE IF ${index}`
65+
})
66+
formData.value.branch = reorderedBranches
67+
refreshBranchAnchors()
68+
},
69+
})
70+
useSortable(branchListRef, sortableBranches, () => ({
71+
disabled: sortableBranches.value.length < 2,
72+
handle: '.condition-branch-handle',
73+
draggable: '> .condition-branch',
74+
}))
75+
5576
// 新分支插入 ELSE 之前,并保留稳定的锚点 ID。
5677
function addBranch() {
5778
const branches = cloneDeep(formData.value.branch)
@@ -134,11 +155,16 @@ onBeforeUnmount(() => {
134155
<template>
135156
<NodeContainer :node-model="model">
136157
<el-form ref="conditionNodeFormRef" :model="formData" label-position="top" require-asterisk-position="right" @submit.prevent>
137-
<template v-for="(branch, branchIndex) in formData.branch" :key="branch.id">
138-
<div class="mb-2 flex items-center gap-1">
139-
<div v-branch-resize="branch.id" class="mk-gray-card min-w-0 flex-1">
140-
<div class="flex-between min-h-6">
141-
<span>{{ branch.type }}</span>
158+
<div ref="branchListRef">
159+
<div v-for="(branch, branchIndex) in sortableBranches" :key="branch.id" class="condition-branch mb-2 flex items-center gap-1">
160+
<div v-branch-resize="branch.id" class="mk-gray-card group/branch min-w-0 flex-1">
161+
<div class="condition-branch-handle flex-between min-h-6">
162+
<div class="flex items-center">
163+
<el-button v-if="sortableBranches.length > 1" link class="hidden! group-hover/branch:inline-flex!" aria-label="拖拽排序分支">
164+
<MkIcon name="icon_drag_outlined" />
165+
</el-button>
166+
<span>{{ branch.type }}</span>
167+
</div>
142168
<div v-if="branch.conditions.length > 1" class="flex items-center gap-2 text-N600">
143169
<span>符合以下</span>
144170
<el-select
@@ -156,63 +182,63 @@ onBeforeUnmount(() => {
156182
</div>
157183
</div>
158184

159-
<template v-if="branchIndex !== formData.branch.length - 1">
160-
<div class="mt-2">
161-
<MkFormList v-model="branch.conditions" :default-item="createCondition()" :first-row-has-label="false" add-text="添加条件">
162-
<template #default="{ item: condition, index: conditionIndex }">
163-
<el-form-item
164-
class="small min-w-0 flex-2"
165-
:prop="`branch.${branchIndex}.conditions.${conditionIndex}.field`"
166-
:rules="{ type: 'array', required: true, message: '请选择变量', trigger: 'change' }"
185+
<div class="mt-2">
186+
<MkFormList v-model="branch.conditions" :default-item="createCondition()" :first-row-has-label="false" add-text="添加条件">
187+
<template #default="{ item: condition, index: conditionIndex }">
188+
<el-form-item
189+
class="small min-w-0 flex-2"
190+
:prop="`branch.${branchIndex}.conditions.${conditionIndex}.field`"
191+
:rules="{ type: 'array', required: true, message: '请选择变量', trigger: 'change' }"
192+
>
193+
<NodeCascader ref="nodeCascaderRefs" v-model="condition.field" :node-model="model" placeholder="请选择变量" />
194+
</el-form-item>
195+
<el-form-item
196+
class="small min-w-0 flex-1"
197+
:prop="`branch.${branchIndex}.conditions.${conditionIndex}.compare`"
198+
:rules="{ required: true, message: '请选择', trigger: 'change' }"
199+
>
200+
<el-select
201+
v-model="condition.compare"
202+
:teleported="false"
203+
placeholder="请选择"
204+
clearable
205+
@change="changeComparison(condition)"
206+
@visible-change="anchorGuard.setOverlayVisible(`${branch.id}:${conditionIndex}:compare`, $event)"
207+
@wheel="handleNodeWheel"
167208
>
168-
<NodeCascader ref="nodeCascaderRefs" v-model="condition.field" :node-model="model" placeholder="请选择变量" />
169-
</el-form-item>
209+
<el-option v-for="comparison in compareList" :key="comparison.value" :label="comparison.label" :value="comparison.value" />
210+
</el-select>
211+
</el-form-item>
212+
<div class="min-w-0 flex-1">
170213
<el-form-item
171-
class="small min-w-0 flex-1"
172-
:prop="`branch.${branchIndex}.conditions.${conditionIndex}.compare`"
173-
:rules="{ required: true, message: '请选择', trigger: 'change' }"
214+
v-if="!valueLessComparisons.has(condition.compare)"
215+
:prop="`branch.${branchIndex}.conditions.${conditionIndex}.value`"
216+
:rules="{ required: true, message: '请输入比较值', trigger: 'blur' }"
217+
class="small"
174218
>
175-
<el-select
176-
v-model="condition.compare"
177-
:teleported="false"
178-
placeholder="请选择"
179-
clearable
180-
@change="changeComparison(condition)"
181-
@visible-change="anchorGuard.setOverlayVisible(`${branch.id}:${conditionIndex}:compare`, $event)"
182-
@wheel="handleNodeWheel"
183-
>
184-
<el-option v-for="comparison in compareList" :key="comparison.value" :label="comparison.label" :value="comparison.value" />
185-
</el-select>
219+
<el-input v-model="condition.value" placeholder="请输入比较值" />
186220
</el-form-item>
187-
<div class="min-w-0 flex-1">
188-
<el-form-item
189-
v-if="!valueLessComparisons.has(condition.compare)"
190-
:prop="`branch.${branchIndex}.conditions.${conditionIndex}.value`"
191-
:rules="{ required: true, message: '请输入比较值', trigger: 'blur' }"
192-
class="small"
193-
>
194-
<el-input v-model="condition.value" placeholder="请输入比较值" />
195-
</el-form-item>
196-
</div>
197-
</template>
198-
</MkFormList>
199-
</div>
200-
</template>
221+
</div>
222+
</template>
223+
</MkFormList>
224+
</div>
201225
</div>
202226
<!-- 删除分支 -->
203227
<div v-if="formData.branch.length > 2" class="h-6 w-6 shrink-0">
204-
<el-button
205-
v-if="branchIndex < formData.branch.length - 1"
206-
text
207-
class="h-6! w-6! p-0!"
208-
aria-label="删除分支"
209-
@click="deleteBranch(branchIndex)"
210-
>
228+
<el-button text class="h-6! w-6! p-0!" aria-label="删除分支" @click="deleteBranch(branchIndex)">
211229
<MkIcon name="icon_delete-trash_outlined" />
212230
</el-button>
213231
</div>
214232
</div>
215-
</template>
233+
</div>
234+
<div v-if="elseBranch" class="mb-2 flex items-center gap-1">
235+
<div :key="elseBranch.id" v-branch-resize="elseBranch.id" class="mk-gray-card min-w-0 flex-1">
236+
<div class="flex-between min-h-6">
237+
<span>{{ elseBranch.type }}</span>
238+
</div>
239+
</div>
240+
<div v-if="formData.branch.length > 2" class="h-6 w-6 shrink-0" />
241+
</div>
216242

217243
<el-button link type="primary" @click="addBranch">
218244
<MkIcon name="icon_add_outlined" class="mr-1" />

0 commit comments

Comments
 (0)