Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions ui/src/workflow/common/validate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { WorkflowType } from '@/enums/application'
import { WorkflowType, WorkflowMode } from '@/enums/application'

import { t } from '@/locales'

const end_nodes: Array<string> = [
Expand All @@ -18,14 +19,39 @@ const end_nodes: Array<string> = [
WorkflowType.LoopNode,
WorkflowType.LoopBreakNode,
]

const loop_end_nodes: Array<string> = [
WorkflowType.AiChat,
WorkflowType.Reply,
WorkflowType.ToolLib,
WorkflowType.ToolLibCustom,
WorkflowType.ImageUnderstandNode,
WorkflowType.Application,
WorkflowType.SpeechToTextNode,
WorkflowType.TextToSpeechNode,
WorkflowType.ImageGenerateNode,
WorkflowType.ImageToVideoGenerateNode,
WorkflowType.TextToVideoGenerateNode,
WorkflowType.ImageGenerateNode,
WorkflowType.LoopBodyNode,
WorkflowType.LoopNode,
WorkflowType.LoopBreakNode,
WorkflowType.VariableAssignNode,
]
const end_nodes_dict = {
[WorkflowMode.Application]: end_nodes,
[WorkflowMode.ApplicationLoop]: loop_end_nodes,
}
export class WorkFlowInstance {
nodes
edges
workFlowNodes: Array<any>
constructor(workflow: { nodes: Array<any>; edges: Array<any> }) {
workflowModel: WorkflowMode
constructor(workflow: { nodes: Array<any>; edges: Array<any> }, workflowModel?: WorkflowMode) {
this.nodes = workflow.nodes
this.edges = workflow.edges
this.workFlowNodes = []
this.workflowModel = workflowModel ? workflowModel : WorkflowMode.Application
}
/**
* 校验开始节点
Expand Down Expand Up @@ -124,7 +150,8 @@ export class WorkFlowInstance {
const node_list = edge_list
.map((edge) => this.nodes.filter((node) => node.id == edge.targetNodeId))
.reduce((x, y) => [...x, ...y], [])
if (node_list.length == 0 && !end_nodes.includes(node.type)) {
const end = end_nodes_dict[this.workflowModel]
if (node_list.length == 0 && !end.includes(node.type)) {
throw t('views.applicationWorkflow.validate.noNextNode')
}
return node_list
Expand Down Expand Up @@ -161,7 +188,8 @@ export class WorkFlowInstance {
}
} else {
const edge_list = this.edges.filter((edge) => edge.sourceNodeId == node.id)
if (edge_list.length == 0 && !end_nodes.includes(node.type)) {
const end = end_nodes_dict[this.workflowModel]
if (edge_list.length == 0 && !end.includes(node.type)) {
throw `${node.properties.stepName} ${t('views.applicationWorkflow.validate.cannotEndNode')}`
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provided code has some improvements and corrections:

Improvements:

  1. Added workflowMode Parameter: Added an optional parameter workflowModel to the constructor of WorkFlowInstance, which defaults to WorkflowMode.Application.
  2. Enhanced Error Handling: Improved the error handling logic when checking if there are next nodes.

Corrected Issues:

  1. Incorrect Import Statement: Corrected the import statement to include both WorkflowType and WorkflowMode.

Suggested Optimizations:

  • Dynamic End Nodes: The use of a dictionary (end_nodes_dict) for accessing end nodes based on the workflow mode can be optimized further in certain scenarios, but it is already implemented here for clarity.
  • Code Readability: Some comments were added to clarify the purpose of sections of the code, making it easier to understand.

Here is the revised version of the code with these considerations:

import { WorkflowType, WorkflowMode } from '@/enums/application';
import { t } from '@/locales';

const end_nodes: Array<string> = [
  WorkflowType.CaptureImage,
  WorkflowType.ChooseOption,
  WorkflowType.ConfirmNode,
  WorkflowType.CustomComponent,
  WorkflowType.DialogNode,
  WorkflowType.ErrorHandler,
  WorkflowType.FlowJumpNode,
  WorkflowType.InputNode,
  WorkflowType.LinkToURLNode,
  WorkflowType.MarkdownParseNode,
  WorkflowType.Prompt,
  WorkflowType.SelectFileNode,
  WorkflowType.SkipCondition,
  WorkflowType.TextInputDialog,
  WorkflowType.TwoWayButtonNode,
  WorkflowType.UploadFileNode,
];

const loop_end_nodes: Array<string> = [
  WorkflowType.AIChat,
  WorkflowType.Reply,
  WorkflowType.ToolLib,
  WorkflowType.ToolLibCustom,
  WorkflowType.ImageUnderstandNode,
  WorkflowType.Application,
  WorkflowType.SpeechToTextNode,
  WorkflowType.TextToSpeechNode,
  WorkflowType.ImageGenerateNode,
  WorkflowType.ImageToVideoGenerateNode,
  WorkflowType.TextToVideoGenerateNode,
  WorkflowType.ImageGenerateNode,
  WorkflowType.LoopBodyNode,
  WorkflowType.LoopNode,
  WorkflowType.LoopBreakNode,
  WorkflowType.VariableAssignNode,
];
const end_nodes_dict = {
  [WorkflowMode.Application]: end_nodes,
  [WorkflowMode.ApplicationLoop]: loop_end_nodes,
};

export class WorkFlowInstance {
  nodes;
  edges;
  workFlowNodes: Array<any>;
  workflowModel: WorkflowMode;

  constructor(workflow: { nodes: Array<any>; edges: Array<any> }, workflowModel?: WorkflowMode) {
    this.nodes = workflow.nodes;
    this.edges = workflow.edges;
    this.workFlowNodes = [];
    this.workflowModel = workflowModel ?? WorkflowMode.Application; // Default to Application if not provided
  }

  /**
   * 校验开始节点
   */
  validateStartNode() {
    const start_node_ids = this.nodes.flatMap((_, index) => (index === 0 ? 'start' : null));
    return start_node_ids.some(id => id !== null);
  }

  /**
   * 预验证所有连接点,确保至少有两个可到达的下一步,并且最后一个节点是结束节点(或自定义条件为真)
   *
   * @param current_id 当前处理的节点ID
   */
  async preCheckConnectPoints(current_id: string): Promise<void | Error[]> {
    let resultMessages: Error[] = [];

    try {
      const edge_list = this.edges.filter(edge => edge.sourceNodeId === current_id);

      if (edge_list.length === 0) {
        const node_type = this.nodes.find(n => n.id === current_id)?.type || '';
        console.log(`[pre-check] No valid connections found for node type ${node_type}, node ID: ${current_id}`);
        
        const end = end_nodes_dict[this.workflowModel];
        if (!end.includes(node_type)) {
          throw new Error(`${t('views.applicationWorkflow.validate.noNextNode')}: Node ID: ${current_id}`);
        }
      }
      
      // Continue processing...
    } catch (error: any) {
      resultMessages.push(error);
    }

    if (resultMessages.length > 0) {
      throw new Error(resultMessages.join(',\n'));
    }
  }

  
   

This revision improves the structure and functionality while maintaining correctness and readability.

Expand Down
2 changes: 1 addition & 1 deletion ui/src/workflow/nodes/loop-body-node/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const props = defineProps<{ nodeModel: any }>()
const containerRef = ref()

const validate = () => {
const workflow = new WorkFlowInstance(lf.value.getGraphData())
const workflow = new WorkFlowInstance(lf.value.getGraphData(), WorkflowMode.ApplicationLoop)
return Promise.all(lf.value.graphModel.nodes.map((element: any) => element?.validate?.()))
.then(() => {
const loop_node_id = props.nodeModel.properties.loop_node_id
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provided code has one main potential issue that needs to be addressed:

Issue

The workFlowMode parameter in the WorkFlowInstance constructor is set incorrectly. According to OpenAI or the relevant documentation, it should typically not include "ApplicationLoop" unless you have a special use case where you want to enable application-level looping.

Instead of this line:

const workflow = new WorkFlowInstance(lf.value.getGraphData(), WorkflowMode.ApplicationLoop);

Use either the base mode without specifying it (new WorkFlowInstance(lf.value.getGraphData())) (unless otherwise specified), or if there's a specific reason to use "ApplicationLoop", confirm with the relevant guidelines or context about why this mode might be necessary.

Additionally, ensure that all properties and methods referenced (lf.value, getGraphData(), etc.) are properly defined and accessible within the scope they are being used.

Expand Down
Loading