diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/CodeBlockNode/types.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/CodeBlockNode/types.ts index 1d4228fe..b982c07d 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/CodeBlockNode/types.ts +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/CodeBlockNode/types.ts @@ -1,9 +1,9 @@ import type { Node } from "@xyflow/react"; +import { NodeBaseData } from "../types"; -export type CodeBlockNodeData = { +export type CodeBlockNodeData = NodeBaseData & { code: string; editable: boolean; - label: string; }; export type CodeBlockNode = Node; @@ -12,4 +12,5 @@ export const codeBlockNodeDefaultData: CodeBlockNodeData = { editable: true, label: "", code: `# To assign a value to the output of this block,\n# assign the value to the variable 'result'\n# The final value of 'result' will be used as the output of this block\n\nresult = 5`, + continueOnFailure: false, } as const; diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/DownloadNode/types.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/DownloadNode/types.ts index ae5f923f..cb00a532 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/DownloadNode/types.ts +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/DownloadNode/types.ts @@ -1,10 +1,10 @@ import type { Node } from "@xyflow/react"; import { SKYVERN_DOWNLOAD_DIRECTORY } from "../../constants"; +import { NodeBaseData } from "../types"; -export type DownloadNodeData = { +export type DownloadNodeData = NodeBaseData & { url: string; editable: boolean; - label: string; }; export type DownloadNode = Node; @@ -13,6 +13,7 @@ export const downloadNodeDefaultData: DownloadNodeData = { editable: true, label: "", url: SKYVERN_DOWNLOAD_DIRECTORY, + continueOnFailure: false, } as const; export const helpTooltipContent = { diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/FileParserNode/types.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/FileParserNode/types.ts index fbdfe8b6..b2892345 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/FileParserNode/types.ts +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/FileParserNode/types.ts @@ -1,9 +1,9 @@ import type { Node } from "@xyflow/react"; +import { NodeBaseData } from "../types"; -export type FileParserNodeData = { +export type FileParserNodeData = NodeBaseData & { fileUrl: string; editable: boolean; - label: string; }; export type FileParserNode = Node; @@ -12,6 +12,7 @@ export const fileParserNodeDefaultData: FileParserNodeData = { editable: true, label: "", fileUrl: "", + continueOnFailure: false, } as const; export const helpTooltipContent = { diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/LoopNode/types.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/LoopNode/types.ts index e2cfa778..a6ac3a70 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/LoopNode/types.ts +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/LoopNode/types.ts @@ -1,9 +1,9 @@ import type { Node } from "@xyflow/react"; +import { NodeBaseData } from "../types"; -export type LoopNodeData = { +export type LoopNodeData = NodeBaseData & { loopValue: string; editable: boolean; - label: string; }; export type LoopNode = Node; @@ -12,6 +12,7 @@ export const loopNodeDefaultData: LoopNodeData = { editable: true, label: "", loopValue: "", + continueOnFailure: false, } as const; export function isLoopNode(node: Node): node is LoopNode { diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/SendEmailNode/types.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/SendEmailNode/types.ts index 12d735e7..a8e7806d 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/SendEmailNode/types.ts +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/SendEmailNode/types.ts @@ -7,14 +7,14 @@ import { SMTP_PORT_PARAMETER_KEY, SMTP_USERNAME_PARAMETER_KEY, } from "../../constants"; +import { NodeBaseData } from "../types"; -export type SendEmailNodeData = { +export type SendEmailNodeData = NodeBaseData & { recipients: string; subject: string; body: string; fileAttachments: string; editable: boolean; - label: string; sender: string; smtpHostSecretParameterKey?: string; smtpPortSecretParameterKey?: string; @@ -36,6 +36,7 @@ export const sendEmailNodeDefaultData: SendEmailNodeData = { smtpPortSecretParameterKey: SMTP_PORT_PARAMETER_KEY, smtpUsernameSecretParameterKey: SMTP_USERNAME_PARAMETER_KEY, smtpPasswordSecretParameterKey: SMTP_PASSWORD_PARAMETER_KEY, + continueOnFailure: false, } as const; export const helpTooltipContent = { diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/TaskNode/types.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/TaskNode/types.ts index 228375e5..d277885a 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/TaskNode/types.ts +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/TaskNode/types.ts @@ -1,6 +1,7 @@ import type { Node } from "@xyflow/react"; +import { NodeBaseData } from "../types"; -export type TaskNodeData = { +export type TaskNodeData = NodeBaseData & { url: string; navigationGoal: string; dataExtractionGoal: string; @@ -11,11 +12,10 @@ export type TaskNodeData = { allowDownloads: boolean; downloadSuffix: string | null; editable: boolean; - label: string; parameterKeys: Array; totpVerificationUrl: string | null; totpIdentifier: string | null; - continueOnFailure: boolean; + cacheActions: boolean; }; export type TaskNode = Node; @@ -38,6 +38,7 @@ export const taskNodeDefaultData: TaskNodeData = { totpVerificationUrl: null, totpIdentifier: null, continueOnFailure: false, + cacheActions: false, } as const; export function isTaskNode(node: Node): node is TaskNode { diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/TextPromptNode/types.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/TextPromptNode/types.ts index fbc76d7a..1445a302 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/TextPromptNode/types.ts +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/TextPromptNode/types.ts @@ -1,6 +1,7 @@ import type { Node } from "@xyflow/react"; +import { NodeBaseData } from "../types"; -export type TextPromptNodeData = { +export type TextPromptNodeData = NodeBaseData & { prompt: string; jsonSchema: string; editable: boolean; @@ -14,6 +15,7 @@ export const textPromptNodeDefaultData: TextPromptNodeData = { label: "", prompt: "", jsonSchema: "null", + continueOnFailure: false, } as const; export const helpTooltipContent = { diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/UploadNode/types.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/UploadNode/types.ts index 8c2a0022..c313a3cb 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/UploadNode/types.ts +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/UploadNode/types.ts @@ -1,10 +1,10 @@ import type { Node } from "@xyflow/react"; import { SKYVERN_DOWNLOAD_DIRECTORY } from "../../constants"; +import { NodeBaseData } from "../types"; -export type UploadNodeData = { +export type UploadNodeData = NodeBaseData & { path: string; editable: boolean; - label: string; }; export type UploadNode = Node; @@ -13,6 +13,7 @@ export const uploadNodeDefaultData: UploadNodeData = { editable: true, label: "", path: SKYVERN_DOWNLOAD_DIRECTORY, + continueOnFailure: false, } as const; export const helpTooltipContent = { diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/types.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/types.ts new file mode 100644 index 00000000..f9e11aae --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/types.ts @@ -0,0 +1,4 @@ +export type NodeBaseData = { + label: string; + continueOnFailure: boolean; +}; diff --git a/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts b/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts index 7bbcf57a..82ccce1d 100644 --- a/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts +++ b/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts @@ -1,7 +1,11 @@ import Dagre from "@dagrejs/dagre"; +import type { Node } from "@xyflow/react"; import { Edge } from "@xyflow/react"; import { nanoid } from "nanoid"; import type { + AWSSecretParameter, + BitwardenSensitiveInformationParameter, + ContextParameter, OutputParameter, Parameter, WorkflowApiResponse, @@ -33,6 +37,7 @@ import { SMTP_USERNAME_AWS_KEY, SMTP_USERNAME_PARAMETER_KEY, } from "./constants"; +import { ParametersState } from "./FlowRenderer"; import { AppNode, nodeTypes } from "./nodes"; import { codeBlockNodeDefaultData } from "./nodes/CodeBlockNode/types"; import { downloadNodeDefaultData } from "./nodes/DownloadNode/types"; @@ -42,8 +47,8 @@ import { NodeAdderNode } from "./nodes/NodeAdderNode/types"; import { sendEmailNodeDefaultData } from "./nodes/SendEmailNode/types"; import { taskNodeDefaultData } from "./nodes/TaskNode/types"; import { textPromptNodeDefaultData } from "./nodes/TextPromptNode/types"; +import { NodeBaseData } from "./nodes/types"; import { uploadNodeDefaultData } from "./nodes/UploadNode/types"; -import type { Node } from "@xyflow/react"; export const NEW_NODE_LABEL_PREFIX = "block_"; @@ -126,6 +131,10 @@ function convertToNode( position: { x: 0, y: 0 }, connectable: false, }; + const commonData: NodeBaseData = { + label: block.label, + continueOnFailure: block.continue_on_failure, + }; switch (block.block_type) { case "task": { return { @@ -133,7 +142,7 @@ function convertToNode( ...common, type: "task", data: { - label: block.label, + ...commonData, editable: true, url: block.url ?? "", navigationGoal: block.navigation_goal ?? "", @@ -147,7 +156,7 @@ function convertToNode( parameterKeys: block.parameters.map((p) => p.key), totpIdentifier: block.totp_identifier ?? null, totpVerificationUrl: block.totp_verification_url ?? null, - continueOnFailure: block.continue_on_failure, + cacheActions: block.cache_actions, }, }; } @@ -157,7 +166,7 @@ function convertToNode( ...common, type: "codeBlock", data: { - label: block.label, + ...commonData, editable: true, code: block.code, }, @@ -169,7 +178,7 @@ function convertToNode( ...common, type: "sendEmail", data: { - label: block.label, + ...commonData, editable: true, body: block.body, fileAttachments: block.file_attachments.join(", "), @@ -189,7 +198,7 @@ function convertToNode( ...common, type: "textPrompt", data: { - label: block.label, + ...commonData, editable: true, prompt: block.prompt, jsonSchema: JSON.stringify(block.json_schema, null, 2), @@ -202,7 +211,7 @@ function convertToNode( ...common, type: "loop", data: { - label: block.label, + ...commonData, editable: true, loopValue: block.loop_over.key, }, @@ -214,7 +223,7 @@ function convertToNode( ...common, type: "fileParser", data: { - label: block.label, + ...commonData, editable: true, fileUrl: block.file_url, }, @@ -227,7 +236,7 @@ function convertToNode( ...common, type: "download", data: { - label: block.label, + ...commonData, editable: true, url: block.url, }, @@ -240,7 +249,7 @@ function convertToNode( ...common, type: "upload", data: { - label: block.label, + ...commonData, editable: true, path: block.path, }, @@ -497,11 +506,15 @@ function JSONParseSafe(json: string): Record | null { function getWorkflowBlock( node: Exclude, ): BlockYAML { + const base = { + label: node.data.label, + continue_on_failure: node.data.continueOnFailure, + }; switch (node.type) { case "task": { return { + ...base, block_type: "task", - label: node.data.label, url: node.data.url, navigation_goal: node.data.navigationGoal, data_extraction_goal: node.data.dataExtractionGoal, @@ -519,13 +532,13 @@ function getWorkflowBlock( parameter_keys: node.data.parameterKeys, totp_identifier: node.data.totpIdentifier, totp_verification_url: node.data.totpVerificationUrl, - continue_on_failure: node.data.continueOnFailure, + cache_actions: node.data.cacheActions, }; } case "sendEmail": { return { + ...base, block_type: "send_email", - label: node.data.label, body: node.data.body, file_attachments: node.data.fileAttachments .split(",") @@ -545,37 +558,37 @@ function getWorkflowBlock( } case "codeBlock": { return { + ...base, block_type: "code", - label: node.data.label, code: node.data.code, }; } case "download": { return { + ...base, block_type: "download_to_s3", - label: node.data.label, url: node.data.url, }; } case "upload": { return { + ...base, block_type: "upload_to_s3", - label: node.data.label, path: node.data.path, }; } case "fileParser": { return { + ...base, block_type: "file_url_parser", - label: node.data.label, file_url: node.data.fileUrl, file_type: "csv", }; } case "textPrompt": { return { + ...base, block_type: "text_prompt", - label: node.data.label, llm_key: "", prompt: node.data.prompt, json_schema: JSONParseSafe(node.data.jsonSchema), @@ -630,13 +643,6 @@ function generateNodeLabel(existingLabels: Array) { throw new Error("Failed to generate a new node label"); } -import type { - AWSSecretParameter, - BitwardenSensitiveInformationParameter, - ContextParameter, -} from "../types/workflowTypes"; -import { ParametersState } from "./FlowRenderer"; - /** * If a parameter is not displayed in the editor, we should echo its value back when saved. */ @@ -980,6 +986,7 @@ function convertBlocks(blocks: Array): Array { parameter_keys: block.parameters.map((p) => p.key), totp_identifier: block.totp_identifier, totp_verification_url: block.totp_verification_url, + cache_actions: block.cache_actions, }; return blockYaml; } @@ -1076,22 +1083,22 @@ function convert(workflow: WorkflowApiResponse): WorkflowCreateYAMLRequest { } export { + convert, + convertEchoParameters, createNode, generateNodeData, - getElements, - getWorkflowBlocks, - layout, generateNodeLabel, - convertEchoParameters, - getOutputParameterKey, - getUpdatedNodesAfterLabelUpdateForParameterKeys, getAdditionalParametersForEmailBlock, - getUniqueLabelForExistingNode, - isOutputParameterKey, + getAvailableOutputParameterKeys, getBlockNameOfOutputParameterKey, getDefaultValueForParameterType, - getUpdatedParametersAfterLabelUpdateForSourceParameterKey, + getElements, + getOutputParameterKey, getPreviousNodeIds, - getAvailableOutputParameterKeys, - convert, + getUniqueLabelForExistingNode, + getUpdatedNodesAfterLabelUpdateForParameterKeys, + getUpdatedParametersAfterLabelUpdateForSourceParameterKey, + getWorkflowBlocks, + isOutputParameterKey, + layout, }; diff --git a/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts b/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts index 6904838d..77cb493a 100644 --- a/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts +++ b/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts @@ -137,6 +137,7 @@ export type TaskBlock = WorkflowBlockBase & { download_suffix?: string | null; totp_verification_url?: string | null; totp_identifier?: string | null; + cache_actions: boolean; }; export type ForLoopBlock = WorkflowBlockBase & { diff --git a/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts b/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts index efb4a470..be4fa884 100644 --- a/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts +++ b/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts @@ -110,6 +110,7 @@ export type TaskBlockYAML = BlockYAMLBase & { download_suffix?: string | null; totp_verification_url?: string | null; totp_identifier?: string | null; + cache_actions: boolean; }; export type CodeBlockYAML = BlockYAMLBase & {