diff --git a/ui/src/locales/lang/en-US/views/application-workflow.ts b/ui/src/locales/lang/en-US/views/application-workflow.ts index b9436061f..63a2a76a7 100644 --- a/ui/src/locales/lang/en-US/views/application-workflow.ts +++ b/ui/src/locales/lang/en-US/views/application-workflow.ts @@ -75,6 +75,7 @@ export default { nodeUnavailable: 'Node unavailable', needConnect1: 'The branch of the node needs to be connected', cannotEndNode: 'This node cannot be used as an end node', + loopNodeBreakNodeRequired: 'Wireless loop must have a Break node', }, nodes: { classify: { diff --git a/ui/src/locales/lang/zh-CN/views/application-workflow.ts b/ui/src/locales/lang/zh-CN/views/application-workflow.ts index 1cfbb4a61..9098ed398 100644 --- a/ui/src/locales/lang/zh-CN/views/application-workflow.ts +++ b/ui/src/locales/lang/zh-CN/views/application-workflow.ts @@ -78,6 +78,7 @@ export default { needConnect1: '节点的', needConnect2: '分支需要连接', cannotEndNode: '节点不能当做结束节点', + loopNodeBreakNodeRequired: '无线循环 必须存在 Break 节点', }, nodes: { classify: { diff --git a/ui/src/locales/lang/zh-Hant/views/application-workflow.ts b/ui/src/locales/lang/zh-Hant/views/application-workflow.ts index 4927a9ac3..9bf00cb0d 100644 --- a/ui/src/locales/lang/zh-Hant/views/application-workflow.ts +++ b/ui/src/locales/lang/zh-Hant/views/application-workflow.ts @@ -76,6 +76,7 @@ export default { needConnect1: '節點的', needConnect2: '分支需要連接', cannotEndNode: '節點不能當做結束節點', + loopNodeBreakNodeRequired: '無線迴圈必須存在Break節點', }, nodes: { classify: { diff --git a/ui/src/workflow/common/validate.ts b/ui/src/workflow/common/validate.ts index 6c27aa933..1254e6d68 100644 --- a/ui/src/workflow/common/validate.ts +++ b/ui/src/workflow/common/validate.ts @@ -15,6 +15,8 @@ const end_nodes: Array = [ WorkflowType.TextToVideoGenerateNode, WorkflowType.ImageGenerateNode, WorkflowType.LoopBodyNode, + WorkflowType.LoopNode, + WorkflowType.LoopBreakNode, ] export class WorkFlowInstance { nodes @@ -29,7 +31,9 @@ export class WorkFlowInstance { * 校验开始节点 */ private is_valid_start_node() { - const start_node_list = this.nodes.filter((item) => item.id === WorkflowType.Start) + const start_node_list = this.nodes.filter((item) => + [WorkflowType.Start, WorkflowType.LoopStartNode].includes(item.id), + ) if (start_node_list.length == 0) { throw t('views.applicationWorkflow.validate.startNodeRequired') } else if (start_node_list.length > 1) { @@ -57,12 +61,20 @@ export class WorkFlowInstance { this.is_valid_nodes() } + is_loop_valid() { + this.is_valid_start_node() + this.is_valid_work_flow() + this.is_valid_nodes() + } + /** * 获取开始节点 * @returns */ get_start_node() { - const start_node_list = this.nodes.filter((item) => item.id === WorkflowType.Start) + const start_node_list = this.nodes.filter((item) => + [WorkflowType.Start, WorkflowType.LoopStartNode].includes(item.id), + ) return start_node_list[0] } /** @@ -73,7 +85,9 @@ export class WorkFlowInstance { const base_node_list = this.nodes.filter((item) => item.id === WorkflowType.Base) return base_node_list[0] } - + extis_break_node() { + return this.nodes.some((item) => item.id === WorkflowType.LoopBreakNode) + } /** * 校验工作流 * @param up_node 上一个节点 @@ -117,7 +131,11 @@ export class WorkFlowInstance { } private is_valid_nodes() { for (const node of this.nodes) { - if (node.type !== WorkflowType.Base && node.type !== WorkflowType.Start) { + if ( + node.type !== WorkflowType.Base && + node.type !== WorkflowType.Start && + node.type !== WorkflowType.LoopStartNode + ) { if (!this.edges.some((edge) => edge.targetNodeId === node.id)) { throw `${t('views.applicationWorkflow.validate.notInWorkFlowNode')}:${node.properties.stepName}` } diff --git a/ui/src/workflow/nodes/loop-body-node/index.vue b/ui/src/workflow/nodes/loop-body-node/index.vue index 69a6bf4eb..d2bcfd39d 100644 --- a/ui/src/workflow/nodes/loop-body-node/index.vue +++ b/ui/src/workflow/nodes/loop-body-node/index.vue @@ -12,17 +12,51 @@ import Dagre from '@/workflow/plugins/dagre' import { initDefaultShortcut } from '@/workflow/common/shortcut' import LoopBodyContainer from '@/workflow/nodes/loop-body-node/LoopBodyContainer.vue' import { WorkflowMode } from '@/enums/application' +import { WorkFlowInstance } from '@/workflow/common/validate' +import { t } from '@/locales' const nodes: any = import.meta.glob('@/workflow/nodes/**/index.ts', { eager: true }) const props = defineProps<{ nodeModel: any }>() const containerRef = ref() const validate = () => { + const workflow = new WorkFlowInstance(lf.value.getGraphData()) return Promise.all(lf.value.graphModel.nodes.map((element: any) => element?.validate?.())) + .then(() => { + const loop_node_id = props.nodeModel.properties.loop_node_id + const loop_node = props.nodeModel.graphModel.getNodeModelById(loop_node_id) + try { + workflow.is_loop_valid() + if (loop_node.properties.node_data.loop_type == 'LOOP' && !workflow.extis_break_node()) { + return Promise.reject({ + node: loop_node, + errMessage: t('views.applicationWorkflow.validate.loopNodeBreakNodeRequired'), + }) + } + + return Promise.resolve({}) + } catch (e) { + return Promise.reject({ node: loop_node, errMessage: e }) + } + }) + .catch((e) => { + props.nodeModel.graphModel.selectNodeById(props.nodeModel.id) + props.nodeModel.graphModel.transformModel.focusOn( + props.nodeModel.x, + props.nodeModel.y, + props.nodeModel.width, + props.nodeModel.height, + ) + throw e + }) } const set_loop_body = () => { const loop_node_id = props.nodeModel.properties.loop_node_id const loop_node = props.nodeModel.graphModel.getNodeModelById(loop_node_id) loop_node.properties.node_data.loop_body = lf.value.getGraphData() + loop_node.properties.node_data.loop = { + x: props.nodeModel.x, + y: props.nodeModel.y, + } } const refresh_loop_fields = (fields: Array) => { diff --git a/ui/src/workflow/nodes/loop-node/index.vue b/ui/src/workflow/nodes/loop-node/index.vue index d3265540b..b7c8a8e0b 100644 --- a/ui/src/workflow/nodes/loop-node/index.vue +++ b/ui/src/workflow/nodes/loop-node/index.vue @@ -120,9 +120,15 @@ onMounted(() => { const nodeOutgoingNode = props.nodeModel.graphModel.getNodeOutgoingNode(props.nodeModel.id) if (!nodeOutgoingNode.some((item: any) => item.type == loopBodyNode.type)) { let workflow = { nodes: [loopStartNode], edges: [] } + let x = props.nodeModel.x + let y = props.nodeModel.y + 200 if (props.nodeModel.properties.node_data.loop_body) { workflow = props.nodeModel.properties.node_data.loop_body } + if (props.nodeModel.properties.node_data.loop) { + x = props.nodeModel.properties.node_data.loop.x + y = props.nodeModel.properties.node_data.loop.y + } const nodeModel = props.nodeModel.graphModel.addNode({ type: loopBodyNode.type, properties: { @@ -130,8 +136,8 @@ onMounted(() => { workflow: workflow, loop_node_id: props.nodeModel.id, }, - x: props.nodeModel.x, - y: props.nodeModel.y + loopBodyNode.height, + x: x, + y: y, }) props.nodeModel.graphModel.addEdge({ type: 'loop-edge',