fix(workflow): 修复工作流节点类型安全和事件处理问题
- 添加对模型类型的安全检查,防止空值导致的错误 - 修复条件节点和循环节点中的数组访问安全性问题 - 统一工作流模式注入和传递机制 - 优化节点拖拽事件处理,确保事件对象正确传递 - 修复循环体节点字段列表获取的安全性检查 - 调整图形渲染逻辑,避免空画布时的视图适配问题 - 更新依赖版本以解决兼容性问题v3.2
parent
8c6ca1206f
commit
eb6f7a3c0a
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
### 1.3 镜像打包
|
||||
|
||||
1. docker build -f installer/Dockerfile -t maxkb:latest
|
||||
1. docker build -f installer/Dockerfile -t maxkb:latest .
|
||||
|
||||
## 2. 数据库外挂配置
|
||||
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@
|
|||
prefix: '/admin',
|
||||
chatPrefix: '/chat',
|
||||
}
|
||||
})()</script><script type="module" crossorigin src="./assets/admin-CkbVdLTL.js"></script><link rel="stylesheet" crossorigin href="./assets/admin-DNnRQ7pR.css"></head><body><div id="app"></div></body></html>
|
||||
})()</script><script type="module" crossorigin src="./assets/admin-4Nw6PvBR.js"></script><link rel="stylesheet" crossorigin href="./assets/admin-Bbyck9zg.css"></head><body><div id="app"></div></body></html>
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
"@codemirror/lang-python": "^6.2.1",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@logicflow/core": "^1.2.27",
|
||||
"@logicflow/extension": "^2.1.15",
|
||||
"@logicflow/extension": "^1.2.27",
|
||||
"@vavt/cm-extension": "^1.9.1",
|
||||
"@vueuse/core": "^13.3.0",
|
||||
"axios": "^1.8.4",
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
class="list-item flex align-center border border-r-6 p-8-12 cursor"
|
||||
style="width: calc(50% - 6px)"
|
||||
@click.stop="clickNodes(item)"
|
||||
@mousedown.stop="onmousedown(item)"
|
||||
@mousedown="onmousedown(item, undefined, undefined, $event)"
|
||||
>
|
||||
<component
|
||||
:is="iconComponent(`${item.type}-icon`)"
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
<NodeContent
|
||||
:list="toolList"
|
||||
@clickNodes="(val: any) => clickNodes(toolLibNode, val, 'tool')"
|
||||
@onmousedown="(val: any) => onmousedown(toolLibNode, val, 'tool')"
|
||||
@onmousedown="(val: any, event: MouseEvent) => onmousedown(toolLibNode, val, 'tool', event)"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</LayoutContainer>
|
||||
|
|
@ -112,7 +112,7 @@
|
|||
<NodeContent
|
||||
:list="applicationList"
|
||||
@clickNodes="(val: any) => clickNodes(applicationNode, val, 'application')"
|
||||
@onmousedown="(val: any) => onmousedown(applicationNode, val, 'application')"
|
||||
@onmousedown="(val: any, event: MouseEvent) => onmousedown(applicationNode, val, 'application', event)"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</LayoutContainer>
|
||||
|
|
@ -208,7 +208,7 @@ function clickNodes(item: any, data?: any, type?: string) {
|
|||
emit('clickNodes', item)
|
||||
}
|
||||
|
||||
function onmousedown(item: any, data?: any, type?: string) {
|
||||
function onmousedown(item: any, data?: any, type?: string, event?: MouseEvent) {
|
||||
if (data) {
|
||||
item['properties']['stepName'] = data.name
|
||||
if (type == 'tool') {
|
||||
|
|
@ -239,7 +239,7 @@ function onmousedown(item: any, data?: any, type?: string) {
|
|||
}
|
||||
}
|
||||
}
|
||||
props.workflowRef?.onmousedown(item)
|
||||
props.workflowRef?.onmousedown(item, event)
|
||||
emit('onmousedown', item)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
class="list-item flex align-center border border-r-6 p-8-12 cursor"
|
||||
style="width: calc(50% - 6px)"
|
||||
@click.stop="emit('clickNodes', item)"
|
||||
@mousedown.stop="emit('onmousedown', item)"
|
||||
@mousedown="emit('onmousedown', item, $event)"
|
||||
>
|
||||
<el-avatar
|
||||
v-if="isAppIcon(item?.icon)"
|
||||
|
|
@ -81,7 +81,7 @@ const props = defineProps<{
|
|||
|
||||
const emit = defineEmits<{
|
||||
(e: 'clickNodes', item: any): void
|
||||
(e: 'onmousedown', item: any): void
|
||||
(e: 'onmousedown', item: any, event: MouseEvent): void
|
||||
}>()
|
||||
|
||||
const filterText = ref('')
|
||||
|
|
|
|||
|
|
@ -159,7 +159,9 @@ import { ComplexPermission } from '@/utils/permission/type'
|
|||
import { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'
|
||||
import permissionMap from '@/permission'
|
||||
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
||||
import { WorkflowMode } from '@/enums/application'
|
||||
provide('getApplicationDetail', () => detail)
|
||||
provide('workflowMode', WorkflowMode.Application)
|
||||
const { theme } = useStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div @mousedown="mousedown" class="workflow-node-container p-16" style="overflow: visible">
|
||||
<div class="workflow-node-container p-16" style="overflow: visible">
|
||||
<div
|
||||
class="step-container app-card p-16"
|
||||
:class="{ isSelected: props.nodeModel.isSelected, error: node_status !== 200 }"
|
||||
|
|
@ -9,10 +9,6 @@
|
|||
<div class="flex-between">
|
||||
<div
|
||||
class="flex align-center"
|
||||
@dragstart.prevent
|
||||
@drag.prevent
|
||||
@dragover.prevent
|
||||
@dragend.prevent
|
||||
style="width: 69%"
|
||||
>
|
||||
<component
|
||||
|
|
@ -24,7 +20,7 @@
|
|||
<h4 class="ellipsis-1 break-all">{{ nodeModel.properties.stepName }}</h4>
|
||||
</div>
|
||||
|
||||
<div @mousemove.stop @mousedown.stop @keydown.stop @click.stop>
|
||||
<div>
|
||||
<el-button text @click="showNode = !showNode">
|
||||
<el-icon class="arrow-icon color-secondary" :class="showNode ? 'rotate-180' : ''"
|
||||
><ArrowDownBold />
|
||||
|
|
@ -78,7 +74,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<el-collapse-transition>
|
||||
<div @mousedown.stop @keydown.stop @click.stop v-show="showNode" class="mt-16">
|
||||
<div v-show="showNode" class="mt-16">
|
||||
<el-alert
|
||||
v-if="node_status != 200"
|
||||
class="mb-16"
|
||||
|
|
@ -124,9 +120,6 @@
|
|||
<el-collapse-transition>
|
||||
<DropdownMenu
|
||||
v-if="showAnchor"
|
||||
@mousemove.stop
|
||||
@mousedown.stop
|
||||
@click.stop
|
||||
@wheel="handleWheel"
|
||||
:show="showAnchor"
|
||||
:id="id"
|
||||
|
|
@ -172,16 +165,18 @@
|
|||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted, provide } from 'vue'
|
||||
import DropdownMenu from '@/views/application-workflow/component/DropdownMenu.vue'
|
||||
import { set } from 'lodash'
|
||||
import { iconComponent } from '../icons/utils'
|
||||
import { copyClick } from '@/utils/clipboard'
|
||||
import { WorkflowType } from '@/enums/application'
|
||||
import { WorkflowType, WorkflowMode } from '@/enums/application'
|
||||
import { MsgError, MsgConfirm } from '@/utils/message'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { t } from '@/locales'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
provide('workflowMode', WorkflowMode.Application)
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id },
|
||||
|
|
|
|||
|
|
@ -62,26 +62,26 @@ class AppNode extends HtmlResize.view {
|
|||
}
|
||||
get_node_field_list() {
|
||||
const result = []
|
||||
if (this.props.model.type === 'start-node') {
|
||||
if (this.props.model.type && this.props.model.type === 'start-node') {
|
||||
result.push({
|
||||
value: 'global',
|
||||
label: t('views.applicationWorkflow.variable.global'),
|
||||
type: 'global',
|
||||
children: this.props.model.properties?.config?.globalFields || [],
|
||||
children: (this.props.model.properties as any)?.config?.globalFields || [],
|
||||
})
|
||||
result.push({
|
||||
value: 'chat',
|
||||
label: t('views.applicationWorkflow.variable.chat'),
|
||||
type: 'chat',
|
||||
children: this.props.model.properties?.config?.chatFields || [],
|
||||
children: (this.props.model.properties as any)?.config?.chatFields || [],
|
||||
})
|
||||
}
|
||||
result.push({
|
||||
value: this.props.model.id,
|
||||
icon: this.props.model.properties.node_data?.icon,
|
||||
icon: (this.props.model.properties as any)?.node_data?.icon,
|
||||
label: this.props.model.properties.stepName,
|
||||
type: this.props.model.type,
|
||||
children: this.props.model.properties?.config?.fields || [],
|
||||
children: (this.props.model.properties as any)?.config?.fields || [],
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
|
@ -107,10 +107,8 @@ class AppNode extends HtmlResize.view {
|
|||
(pre, next) => [...pre, ...next],
|
||||
[],
|
||||
)
|
||||
const start_node_field_list = (
|
||||
this.props.graphModel.getNodeModelById('start-node') ||
|
||||
this.props.graphModel.getNodeModelById('loop-start-node')
|
||||
).get_node_field_list()
|
||||
const startNode = this.props.graphModel.getNodeModelById('start-node') || this.props.graphModel.getNodeModelById('loop-start-node')
|
||||
const start_node_field_list = startNode ? startNode.get_node_field_list() : []
|
||||
return [...start_node_field_list, ...result]
|
||||
}
|
||||
|
||||
|
|
@ -228,8 +226,8 @@ class AppNode extends HtmlResize.view {
|
|||
this.targetId(),
|
||||
this.component,
|
||||
root,
|
||||
model,
|
||||
graphModel,
|
||||
model as any,
|
||||
graphModel as any,
|
||||
undefined,
|
||||
this.props.graphModel.get_provide,
|
||||
)
|
||||
|
|
@ -334,8 +332,10 @@ class AppNodeModel extends HtmlResize.model {
|
|||
const style = super.getAnchorStyle(anchorInfo)
|
||||
if (anchorInfo.type === 'left') {
|
||||
style.fill = 'red'
|
||||
style.hover.fill = 'transparent'
|
||||
style.hover.stroke = 'transpanrent'
|
||||
if (style.hover) {
|
||||
style.hover.fill = 'transparent'
|
||||
style.hover.stroke = 'transpanrent'
|
||||
}
|
||||
style.className = 'lf-hide-default'
|
||||
} else {
|
||||
style.fill = 'green'
|
||||
|
|
@ -414,8 +414,8 @@ class AppNodeModel extends HtmlResize.model {
|
|||
const showNode = this.properties.showNode === undefined ? true : this.properties.showNode
|
||||
const anchors: any = []
|
||||
|
||||
if (this.type !== WorkflowType.Base) {
|
||||
if (![WorkflowType.Start, WorkflowType.LoopStartNode.toString()].includes(this.type)) {
|
||||
if (this.type && this.type !== WorkflowType.Base.toString()) {
|
||||
if (![WorkflowType.Start.toString(), WorkflowType.LoopStartNode.toString()].includes(this.type)) {
|
||||
anchors.push({
|
||||
x: x - width / 2 + 10,
|
||||
y: showNode ? y : y - 15,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
</template>
|
||||
<script setup lang="ts">
|
||||
import LogicFlow from '@logicflow/core'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, onMounted, onUnmounted, inject } from 'vue'
|
||||
import AppEdge from './common/edge'
|
||||
import loopEdge from './common/loopEdge'
|
||||
import Control from './common/NodeControl.vue'
|
||||
|
|
@ -18,6 +18,8 @@ import Dagre from '@/workflow/plugins/dagre'
|
|||
import { disconnectAll, getTeleport } from '@/workflow/common/teleport'
|
||||
import { WorkflowMode } from '@/enums/application'
|
||||
const nodes: any = import.meta.glob('./nodes/**/index.ts', { eager: true })
|
||||
const workflow_mode = inject('workflowMode') || WorkflowMode.Application
|
||||
const loop_workflow_mode = inject('loopWorkflowMode') || WorkflowMode.ApplicationLoop
|
||||
|
||||
defineOptions({ name: 'WorkFlow' })
|
||||
const TeleportContainer = getTeleport()
|
||||
|
|
@ -71,6 +73,8 @@ const renderGraphData = (data?: any) => {
|
|||
},
|
||||
isSilentMode: false,
|
||||
container: container,
|
||||
stopMoveGraph: false,
|
||||
stopZoomGraph: false,
|
||||
})
|
||||
lf.value.setTheme({
|
||||
bezier: {
|
||||
|
|
@ -78,7 +82,6 @@ const renderGraphData = (data?: any) => {
|
|||
strokeWidth: 1,
|
||||
},
|
||||
})
|
||||
lf.value.graphModel.get = 'sdasdaad'
|
||||
lf.value.on('graph:rendered', () => {
|
||||
flowId.value = lf.value.graphModel.flowId
|
||||
})
|
||||
|
|
@ -96,7 +99,8 @@ const renderGraphData = (data?: any) => {
|
|||
return {
|
||||
getNode: () => node,
|
||||
getGraph: () => graph,
|
||||
workflowMode: WorkflowMode.Application,
|
||||
workflowMode: workflow_mode,
|
||||
loopWorkflowMode: loop_workflow_mode,
|
||||
}
|
||||
}
|
||||
lf.value.graphModel.eventCenter.on('delete_edge', (id_list: Array<string>) => {
|
||||
|
|
@ -108,10 +112,12 @@ const renderGraphData = (data?: any) => {
|
|||
// 清除当前节点下面的子节点的所有缓存
|
||||
data.nodeModel.clear_next_node_field(false)
|
||||
})
|
||||
// lf.value.openSelectionSelect()
|
||||
// lf.value.extension.selectionSelect.setSelectionSense(true, false)
|
||||
setTimeout(() => {
|
||||
lf.value?.fitView()
|
||||
if (lf.value.graphModel?.nodes.length > 1) {
|
||||
lf.value?.fitView()
|
||||
} else {
|
||||
lf.value?.translateCenter()
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
|
|
@ -133,12 +139,12 @@ const getGraphData = () => {
|
|||
return _graph_data
|
||||
}
|
||||
|
||||
const onmousedown = (shapeItem: ShapeItem) => {
|
||||
const onmousedown = (shapeItem: ShapeItem, event?: MouseEvent) => {
|
||||
if (shapeItem.type) {
|
||||
lf.value.dnd.startDrag({
|
||||
type: shapeItem.type,
|
||||
properties: { ...shapeItem.properties },
|
||||
})
|
||||
}, event)
|
||||
}
|
||||
|
||||
if (shapeItem.callback) {
|
||||
|
|
|
|||
|
|
@ -45,13 +45,14 @@ class ConditionModel extends AppNodeModel {
|
|||
type: 'left'
|
||||
})
|
||||
|
||||
if (branch_condition_list) {
|
||||
for (let index = 0; index < branch_condition_list.length; index++) {
|
||||
const element = branch_condition_list[index]
|
||||
const h = get_up_index_height(branch_condition_list, index)
|
||||
const conditionList = Array.isArray(branch_condition_list) ? branch_condition_list : []
|
||||
if (conditionList.length > 0) {
|
||||
for (let index = 0; index < conditionList.length; index++) {
|
||||
const element = conditionList[index]
|
||||
const h = get_up_index_height(conditionList, index)
|
||||
anchors.push({
|
||||
x: x + width / 2 - 10,
|
||||
y: showNode ? y - height / 2 + 75 + h + element.height / 2 : y - 15,
|
||||
y: showNode ? y - height / 2 + 75 + h + (element.height || 0) / 2 : y - 15,
|
||||
id: `${id}_${element.id}_right`,
|
||||
type: 'right'
|
||||
})
|
||||
|
|
|
|||
|
|
@ -46,12 +46,13 @@ class IntentModel extends AppNodeModel {
|
|||
type: 'left'
|
||||
})
|
||||
|
||||
if (branch_condition_list) {
|
||||
const conditionList = Array.isArray(branch_condition_list) ? branch_condition_list : []
|
||||
if (conditionList.length > 0) {
|
||||
|
||||
const FORM_ITEMS_HEIGHT = 397 // 上方表单占用高度
|
||||
|
||||
for (let index = 0; index < branch_condition_list.length; index++) {
|
||||
const element = branch_condition_list[index]
|
||||
for (let index = 0; index < conditionList.length; index++) {
|
||||
const element = conditionList[index]
|
||||
|
||||
anchors.push({
|
||||
x: x + width / 2 - 10,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div @mousedown="mousedown" class="workflow-node-container p-16" style="overflow: visible">
|
||||
<div class="workflow-node-container p-16" style="overflow: visible">
|
||||
<div
|
||||
class="step-container app-card p-16"
|
||||
:class="{ isSelected: props.nodeModel.isSelected, error: node_status !== 200 }"
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
</el-button>
|
||||
</div>
|
||||
<el-collapse-transition>
|
||||
<div @mousedown.stop @keydown.stop @click.stop v-show="showNode" class="mt-16">
|
||||
<div v-show="showNode" class="mt-16">
|
||||
<el-alert
|
||||
v-if="node_status != 200"
|
||||
class="mb-16"
|
||||
|
|
|
|||
|
|
@ -10,8 +10,13 @@ class LoopBodyNodeView extends AppNode {
|
|||
}
|
||||
get_up_node_field_list(contain_self: boolean, use_cache: boolean) {
|
||||
const loop_node_id = this.props.model.properties.loop_node_id
|
||||
const loop_node = this.props.graphModel.getNodeModelById(loop_node_id)
|
||||
return loop_node.get_up_node_field_list(contain_self, use_cache)
|
||||
if (typeof loop_node_id === 'string') {
|
||||
const loop_node = this.props.graphModel.getNodeModelById(loop_node_id)
|
||||
if (loop_node && typeof loop_node.get_up_node_field_list === 'function') {
|
||||
return loop_node.get_up_node_field_list(contain_self, use_cache)
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
}
|
||||
class LoopBodyModel extends AppNodeModel {
|
||||
|
|
|
|||
|
|
@ -121,13 +121,6 @@ const renderGraphData = (data?: any) => {
|
|||
)
|
||||
|
||||
initDefaultShortcut(lf.value, lf.value.graphModel)
|
||||
lf.value.graphModel.get_provide = (node: any, graph: any) => {
|
||||
return {
|
||||
getNode: () => node,
|
||||
getGraph: () => graph,
|
||||
workflowMode: WorkflowMode.ApplicationLoop,
|
||||
}
|
||||
}
|
||||
lf.value.graphModel.refresh_loop_fields = refresh_loop_fields
|
||||
lf.value.graphModel.get_parent_nodes = () => {
|
||||
return props.nodeModel.graphModel.nodes
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ class LoopModel extends AppNodeModel {
|
|||
const showNode = this.properties.showNode === undefined ? true : this.properties.showNode
|
||||
const anchors: any = []
|
||||
|
||||
if (this.type !== WorkflowType.Base) {
|
||||
if (this.type !== WorkflowType.Start) {
|
||||
if (this.type && this.type !== WorkflowType.Base.toString()) {
|
||||
if (this.type !== WorkflowType.Start.toString()) {
|
||||
anchors.push({
|
||||
x: x - width / 2 + 10,
|
||||
y: showNode ? y : y - 15,
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ class LoopStartNode extends AppNode {
|
|||
}
|
||||
get_node_field_list() {
|
||||
const result = []
|
||||
if (this.props.model.type === 'loop-start-node') {
|
||||
if (this.props.model.type && this.props.model.type === 'loop-start-node') {
|
||||
result.push({
|
||||
value: 'loop',
|
||||
label: t('views.applicationWorkflow.variable.loop'),
|
||||
type: 'loop',
|
||||
children:
|
||||
(this.props.model.properties.loop_input_field_list
|
||||
(Array.isArray(this.props.model.properties.loop_input_field_list)
|
||||
? this.props.model.properties.loop_input_field_list
|
||||
: []
|
||||
).map((i: any) => {
|
||||
|
|
@ -27,9 +27,10 @@ class LoopStartNode extends AppNode {
|
|||
|
||||
result.push({
|
||||
value: this.props.model.id,
|
||||
icon: (this.props.model.properties as any)?.node_data?.icon,
|
||||
label: this.props.model.properties.stepName,
|
||||
type: this.props.model.type,
|
||||
children: this.props.model.properties?.config?.fields || [],
|
||||
children: (this.props.model.properties as any)?.config?.fields || [],
|
||||
})
|
||||
|
||||
return result
|
||||
|
|
|
|||
Loading…
Reference in New Issue