From d52025472f620d2988f67bede086285327a0bb58 Mon Sep 17 00:00:00 2001 From: Shuchang Zheng Date: Mon, 25 Nov 2024 08:37:55 -0800 Subject: [PATCH] Add navigation block (#1256) Co-authored-by: Muhammed Salih Altun --- .../src/components/icons/RobotIcon.tsx | 23 ++ .../src/routes/workflows/editor/constants.ts | 30 ++ .../editor/nodes/ActionNode/ActionNode.tsx | 45 +- .../editor/nodes/ActionNode/types.ts | 31 -- .../nodes/NavigationNode/NavigationNode.tsx | 383 ++++++++++++++++++ .../editor/nodes/NavigationNode/types.ts | 39 ++ .../routes/workflows/editor/nodes/index.ts | 8 +- .../panels/WorkflowNodeLibraryPanel.tsx | 7 + .../workflows/editor/workflowEditorUtils.ts | 86 +++- .../routes/workflows/types/workflowTypes.ts | 36 +- .../workflows/types/workflowYamlTypes.ts | 19 +- 11 files changed, 650 insertions(+), 57 deletions(-) create mode 100644 skyvern-frontend/src/components/icons/RobotIcon.tsx create mode 100644 skyvern-frontend/src/routes/workflows/editor/nodes/NavigationNode/NavigationNode.tsx create mode 100644 skyvern-frontend/src/routes/workflows/editor/nodes/NavigationNode/types.ts diff --git a/skyvern-frontend/src/components/icons/RobotIcon.tsx b/skyvern-frontend/src/components/icons/RobotIcon.tsx new file mode 100644 index 00000000..05420fff --- /dev/null +++ b/skyvern-frontend/src/components/icons/RobotIcon.tsx @@ -0,0 +1,23 @@ +type Props = { + className?: string; +}; + +function RobotIcon({ className }: Props) { + return ( + + + + ); +} + +export { RobotIcon }; diff --git a/skyvern-frontend/src/routes/workflows/editor/constants.ts b/skyvern-frontend/src/routes/workflows/editor/constants.ts index 4a38ffed..bc219a47 100644 --- a/skyvern-frontend/src/routes/workflows/editor/constants.ts +++ b/skyvern-frontend/src/routes/workflows/editor/constants.ts @@ -14,3 +14,33 @@ export const SMTP_USERNAME_AWS_KEY = "SKYVERN_SMTP_USERNAME_SES"; export const SMTP_PASSWORD_AWS_KEY = "SKYVERN_SMTP_PASSWORD_SES"; export const EMAIL_BLOCK_SENDER = "hello@skyvern.com"; + +export const commonHelpTooltipContent = { + maxRetries: + "Specify how many times you would like a task to retry upon failure.", + maxStepsOverride: + "Specify the maximum number of steps a task can take in total.", + completeOnDownload: + "Allow Skyvern to auto-complete the task when it downloads a file.", + fileSuffix: + "A file suffix that's automatically added to all downloaded files.", + errorCodeMapping: + "Knowing about why a task terminated can be important, specify error messages here.", + totpVerificationUrl: + "If you have an internal system for storing TOTP codes, link the endpoint here.", + totpIdentifier: + "If you are running multiple tasks or workflows at once, you will need to give the task an identifier to know that this TOTP goes with this task.", + continueOnFailure: + "Allow the workflow to continue if it encounters a failure.", + cacheActions: "Cache the actions of this task.", +} as const; + +export const commonFieldPlaceholders = { + url: "https://", + navigationGoal: 'Input text into "Name" field.', + maxRetries: "Default: 3", + maxStepsOverride: "Default: 10", + downloadSuffix: "Add an ID for downloaded files", + totpVerificationUrl: "Provide your 2FA endpoint", + totpIdentifier: "Add an ID that links your TOTP to the task", +} as const; diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/ActionNode/ActionNode.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/ActionNode/ActionNode.tsx index ab8fd53e..f059f5d2 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/ActionNode/ActionNode.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/ActionNode/ActionNode.tsx @@ -13,15 +13,23 @@ import { Handle, NodeProps, Position, useReactFlow } from "@xyflow/react"; import { useState } from "react"; import { EditableNodeTitle } from "../components/EditableNodeTitle"; import { NodeActionMenu } from "../NodeActionMenu"; -import { helpTooltipContent, type ActionNode } from "./types"; +import type { ActionNode } from "./types"; import { HelpTooltip } from "@/components/HelpTooltip"; import { Input } from "@/components/ui/input"; -import { fieldPlaceholders } from "./types"; import { Checkbox } from "@/components/ui/checkbox"; import { errorMappingExampleValue } from "../types"; import { CodeEditor } from "@/routes/workflows/components/CodeEditor"; import { Switch } from "@/components/ui/switch"; import { ClickIcon } from "@/components/icons/ClickIcon"; +import { + commonFieldPlaceholders, + commonHelpTooltipContent, +} from "../../constants"; + +const navigationGoalTooltip = + "Specify a single step or action you'd like Skyvern to complete. Actions are one-off tasks like filling a field or interacting with a specific element on the page.\n\nCurrently supported actions are click, input text, upload file, and select."; + +const navigationGoalPlaceholder = 'Input text into "Name" field.'; function ActionNode({ id, data }: NodeProps) { const { updateNodeData } = useReactFlow(); @@ -91,7 +99,7 @@ function ActionNode({ id, data }: NodeProps) {
- +
{ @@ -101,6 +109,7 @@ function ActionNode({ id, data }: NodeProps) { handleChange("navigationGoal", event.target.value); }} value={inputs.navigationGoal} + placeholder={navigationGoalPlaceholder} className="nopan text-xs" />
@@ -117,11 +126,13 @@ function ActionNode({ id, data }: NodeProps) { - + ) { Error Messages ) { Continue on Failure
@@ -207,7 +218,9 @@ function ActionNode({ id, data }: NodeProps) { - +
) { Complete on Download
@@ -248,11 +261,13 @@ function ActionNode({ id, data }: NodeProps) { - +
{ @@ -270,7 +285,7 @@ function ActionNode({ id, data }: NodeProps) { 2FA Verification URL ) { handleChange("totpVerificationUrl", event.target.value); }} value={inputs.totpVerificationUrl ?? ""} - placeholder={fieldPlaceholders["totpVerificationUrl"]} + placeholder={commonFieldPlaceholders["totpVerificationUrl"]} className="nopan text-xs" /> @@ -291,7 +306,7 @@ function ActionNode({ id, data }: NodeProps) { 2FA Identifier ) { handleChange("totpIdentifier", event.target.value); }} value={inputs.totpIdentifier ?? ""} - placeholder={fieldPlaceholders["totpIdentifier"]} + placeholder={commonFieldPlaceholders["totpIdentifier"]} className="nopan text-xs" /> diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/ActionNode/types.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/ActionNode/types.ts index e564c98b..71cef107 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/ActionNode/types.ts +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/ActionNode/types.ts @@ -35,34 +35,3 @@ export const actionNodeDefaultData: ActionNodeData = { export function isActionNode(node: Node): node is ActionNode { return node.type === "action"; } - -export const helpTooltipContent = { - navigationGoal: - "Specify a single step or action you'd like Skyvern to complete. Actions are one-off tasks like filling a field or interacting with a specific element on the page.\n\nCurrently supported actions are click, input text, upload file, and select.", - maxRetries: - "Specify how many times you would like a task to retry upon failure.", - maxStepsOverride: - "Specify the maximum number of steps a task can take in total.", - completeOnDownload: - "Allow Skyvern to auto-complete the task when it downloads a file.", - fileSuffix: - "A file suffix that's automatically added to all downloaded files.", - errorCodeMapping: - "Knowing about why a task terminated can be important, specify error messages here.", - totpVerificationUrl: - "If you have an internal system for storing TOTP codes, link the endpoint here.", - totpIdentifier: - "If you are running multiple tasks or workflows at once, you will need to give the task an identifier to know that this TOTP goes with this task.", - continueOnFailure: - "Allow the workflow to continue if it encounters a failure.", - cacheActions: "Cache the actions of this task.", -} as const; - -export const fieldPlaceholders = { - navigationGoal: 'Input text into "Name" field.', - maxRetries: "Default: 3", - maxStepsOverride: "Default: 10", - downloadSuffix: "Add an ID for downloaded files", - totpVerificationUrl: "Provide your 2FA endpoint", - totpIdentifier: "Add an ID that links your TOTP to the task", -}; diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/NavigationNode/NavigationNode.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/NavigationNode/NavigationNode.tsx new file mode 100644 index 00000000..e780a1c7 --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/NavigationNode/NavigationNode.tsx @@ -0,0 +1,383 @@ +import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { Label } from "@/components/ui/label"; +import { Separator } from "@/components/ui/separator"; +import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback"; +import { useNodeLabelChangeHandler } from "@/routes/workflows/hooks/useLabelChangeHandler"; +import { Handle, NodeProps, Position, useReactFlow } from "@xyflow/react"; +import { useState } from "react"; +import { EditableNodeTitle } from "../components/EditableNodeTitle"; +import { NodeActionMenu } from "../NodeActionMenu"; +import { HelpTooltip } from "@/components/HelpTooltip"; +import { Input } from "@/components/ui/input"; +import { Checkbox } from "@/components/ui/checkbox"; +import { errorMappingExampleValue } from "../types"; +import { CodeEditor } from "@/routes/workflows/components/CodeEditor"; +import { Switch } from "@/components/ui/switch"; +import type { NavigationNode } from "./types"; +import { + commonFieldPlaceholders, + commonHelpTooltipContent, +} from "../../constants"; +import { RobotIcon } from "@/components/icons/RobotIcon"; + +const urlTooltip = + "The URL Skyvern is navigating to. Leave this field blank to pick up from where the last block left off."; +const urlPlaceholder = "https://"; +const navigationGoalTooltip = + "Give Skyvern an objective. Make sure to include when the task is complete, when it should self-terminate, and any guardrails."; +const navigationGoalPlaceholder = "Tell Skyvern what to do."; + +function NavigationNode({ id, data }: NodeProps) { + const { updateNodeData } = useReactFlow(); + const { editable } = data; + const [label, setLabel] = useNodeLabelChangeHandler({ + id, + initialValue: data.label, + }); + const [inputs, setInputs] = useState({ + url: data.url, + navigationGoal: data.navigationGoal, + errorCodeMapping: data.errorCodeMapping, + maxRetries: data.maxRetries, + maxStepsOverride: data.maxStepsOverride, + allowDownloads: data.allowDownloads, + continueOnFailure: data.continueOnFailure, + cacheActions: data.cacheActions, + downloadSuffix: data.downloadSuffix, + totpVerificationUrl: data.totpVerificationUrl, + totpIdentifier: data.totpIdentifier, + }); + const deleteNodeCallback = useDeleteNodeCallback(); + + function handleChange(key: string, value: unknown) { + if (!editable) { + return; + } + setInputs({ ...inputs, [key]: value }); + updateNodeData(id, { [key]: value }); + } + + return ( +
+ + +
+
+
+
+ +
+
+ + Navigation Block +
+
+ { + deleteNodeCallback(id); + }} + /> +
+
+
+
+ + +
+ { + if (!editable) { + return; + } + handleChange("url", event.target.value); + }} + value={inputs.url} + placeholder={urlPlaceholder} + className="nopan text-xs" + /> +
+
+
+ + +
+ { + if (!editable) { + return; + } + handleChange("navigationGoal", event.target.value); + }} + value={inputs.navigationGoal} + placeholder={navigationGoalPlaceholder} + className="nopan text-xs" + /> +
+
+ + + + + Advanced Settings + + +
+
+
+ + +
+ { + if (!editable) { + return; + } + const value = + event.target.value === "" + ? null + : Number(event.target.value); + handleChange("maxRetries", value); + }} + /> +
+
+
+ + +
+ { + if (!editable) { + return; + } + const value = + event.target.value === "" + ? null + : Number(event.target.value); + handleChange("maxStepsOverride", value); + }} + /> +
+
+
+
+ + +
+ { + if (!editable) { + return; + } + handleChange( + "errorCodeMapping", + checked + ? JSON.stringify(errorMappingExampleValue, null, 2) + : "null", + ); + }} + /> +
+ {inputs.errorCodeMapping !== "null" && ( +
+ { + if (!editable) { + return; + } + handleChange("errorCodeMapping", value); + }} + className="nowheel nopan" + fontSize={8} + /> +
+ )} +
+ +
+
+ + +
+
+ { + if (!editable) { + return; + } + handleChange("continueOnFailure", checked); + }} + /> +
+
+
+
+ + +
+
+ { + if (!editable) { + return; + } + handleChange("cacheActions", checked); + }} + /> +
+
+ +
+
+ + +
+
+ { + if (!editable) { + return; + } + handleChange("allowDownloads", checked); + }} + /> +
+
+
+
+ + +
+ { + if (!editable) { + return; + } + handleChange("downloadSuffix", event.target.value); + }} + /> +
+ +
+
+ + +
+ { + if (!editable) { + return; + } + handleChange("totpVerificationUrl", event.target.value); + }} + value={inputs.totpVerificationUrl ?? ""} + placeholder={commonFieldPlaceholders["totpVerificationUrl"]} + className="nopan text-xs" + /> +
+
+
+ + +
+ { + if (!editable) { + return; + } + handleChange("totpIdentifier", event.target.value); + }} + value={inputs.totpIdentifier ?? ""} + placeholder={commonFieldPlaceholders["totpIdentifier"]} + className="nopan text-xs" + /> +
+
+
+
+
+
+
+ ); +} + +export { NavigationNode }; diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/NavigationNode/types.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/NavigationNode/types.ts new file mode 100644 index 00000000..8f19a747 --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/NavigationNode/types.ts @@ -0,0 +1,39 @@ +import type { Node } from "@xyflow/react"; +import { NodeBaseData } from "../types"; + +export type NavigationNodeData = NodeBaseData & { + url: string; + navigationGoal: string; + errorCodeMapping: string; + maxRetries: number | null; + maxStepsOverride: number | null; + allowDownloads: boolean; + downloadSuffix: string | null; + parameterKeys: Array; + totpVerificationUrl: string | null; + totpIdentifier: string | null; + cacheActions: boolean; +}; + +export type NavigationNode = Node; + +export const navigationNodeDefaultData: NavigationNodeData = { + label: "", + url: "", + navigationGoal: "", + errorCodeMapping: "null", + maxRetries: null, + maxStepsOverride: null, + allowDownloads: false, + downloadSuffix: null, + editable: true, + parameterKeys: [], + totpVerificationUrl: null, + totpIdentifier: null, + continueOnFailure: false, + cacheActions: false, +} as const; + +export function isNavigationNode(node: Node): node is NavigationNode { + return node.type === "navigation"; +} diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/index.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/index.ts index 82222e96..7b152509 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/index.ts +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/index.ts @@ -23,6 +23,8 @@ import type { ValidationNode } from "./ValidationNode/types"; import { ValidationNode as ValidationNodeComponent } from "./ValidationNode/ValidationNode"; import type { ActionNode } from "./ActionNode/types"; import { ActionNode as ActionNodeComponent } from "./ActionNode/ActionNode"; +import { NavigationNode } from "./NavigationNode/types"; +import { NavigationNode as NavigationNodeComponent } from "./NavigationNode/NavigationNode"; export type UtilityNode = StartNode | NodeAdderNode; @@ -36,7 +38,8 @@ export type WorkflowBlockNode = | UploadNode | DownloadNode | ValidationNode - | ActionNode; + | ActionNode + | NavigationNode; export function isUtilityNode(node: AppNode): node is UtilityNode { return node.type === "nodeAdder" || node.type === "start"; @@ -61,4 +64,5 @@ export const nodeTypes = { start: memo(StartNodeComponent), validation: memo(ValidationNodeComponent), action: memo(ActionNodeComponent), -}; + navigation: memo(NavigationNodeComponent), +} as const; diff --git a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowNodeLibraryPanel.tsx b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowNodeLibraryPanel.tsx index e4c87302..5268f8be 100644 --- a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowNodeLibraryPanel.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowNodeLibraryPanel.tsx @@ -14,6 +14,7 @@ import { WorkflowBlockNode } from "../nodes"; import { AddNodeProps } from "../FlowRenderer"; import { ClickIcon } from "@/components/icons/ClickIcon"; import { ScrollArea, ScrollAreaViewport } from "@/components/ui/scroll-area"; +import { RobotIcon } from "@/components/icons/RobotIcon"; const nodeLibraryItems: Array<{ nodeType: NonNullable; @@ -83,6 +84,12 @@ const nodeLibraryItems: Array<{ title: "Action Block", description: "Take a single action", }, + { + nodeType: "navigation", + icon: , + title: "Navigation Block", + description: "Navigate on the page", + }, ]; type Props = { diff --git a/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts b/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts index ceb9454b..2a584983 100644 --- a/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts +++ b/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts @@ -23,6 +23,7 @@ import { TextPromptBlockYAML, UploadToS3BlockYAML, ValidationBlockYAML, + NavigationBlockYAML, WorkflowCreateYAMLRequest, } from "../types/workflowYamlTypes"; import { @@ -52,6 +53,7 @@ import { NodeBaseData } from "./nodes/types"; import { uploadNodeDefaultData } from "./nodes/UploadNode/types"; import { validationNodeDefaultData } from "./nodes/ValidationNode/types"; import { actionNodeDefaultData } from "./nodes/ActionNode/types"; +import { navigationNodeDefaultData } from "./nodes/NavigationNode/types"; export const NEW_NODE_LABEL_PREFIX = "block_"; @@ -197,6 +199,27 @@ function convertToNode( }, }; } + case "navigation": { + return { + ...identifiers, + ...common, + type: "navigation", + data: { + ...commonData, + url: block.url ?? "", + navigationGoal: block.navigation_goal ?? "", + errorCodeMapping: JSON.stringify(block.error_code_mapping, null, 2), + allowDownloads: block.complete_on_download ?? false, + downloadSuffix: block.download_suffix ?? null, + maxRetries: block.max_retries ?? null, + parameterKeys: block.parameters.map((p) => p.key), + totpIdentifier: block.totp_identifier ?? null, + totpVerificationUrl: block.totp_verification_url ?? null, + cacheActions: block.cache_actions, + maxStepsOverride: block.max_steps_per_run ?? null, + }, + }; + } case "code": { return { ...identifiers, @@ -509,6 +532,17 @@ function createNode( }, }; } + case "navigation": { + return { + ...identifiers, + ...common, + type: "navigation", + data: { + ...navigationNodeDefaultData, + label, + }, + }; + } case "loop": { return { ...identifiers, @@ -662,6 +696,28 @@ function getWorkflowBlock(node: WorkflowBlockNode): BlockYAML { cache_actions: node.data.cacheActions, }; } + case "navigation": { + return { + ...base, + block_type: "navigation", + navigation_goal: node.data.navigationGoal, + error_code_mapping: JSONParseSafe(node.data.errorCodeMapping) as Record< + string, + string + > | null, + url: node.data.url, + ...(node.data.maxRetries !== null && { + max_retries: node.data.maxRetries, + }), + max_steps_per_run: node.data.maxStepsOverride, + complete_on_download: node.data.allowDownloads, + download_suffix: node.data.downloadSuffix, + parameter_keys: node.data.parameterKeys, + totp_identifier: node.data.totpIdentifier, + totp_verification_url: node.data.totpVerificationUrl, + cache_actions: node.data.cacheActions, + }; + } case "sendEmail": { return { ...base, @@ -1042,7 +1098,7 @@ function getAvailableOutputParameterKeys( return outputParameterKeys; } -function convertParameters( +function convertParametersToParameterYAML( parameters: Array>, ): Array { return parameters.map((parameter) => { @@ -1105,7 +1161,9 @@ function convertParameters( }); } -function convertBlocks(blocks: Array): Array { +function convertBlocksToBlockYAML( + blocks: Array, +): Array { return blocks.map((block) => { const base = { label: block.label, @@ -1160,12 +1218,30 @@ function convertBlocks(blocks: Array): Array { }; return blockYaml; } + case "navigation": { + const blockYaml: NavigationBlockYAML = { + ...base, + block_type: "navigation", + url: block.url, + navigation_goal: block.navigation_goal, + error_code_mapping: block.error_code_mapping, + max_retries: block.max_retries, + max_steps_per_run: block.max_steps_per_run, + complete_on_download: block.complete_on_download, + download_suffix: block.download_suffix, + 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; + } case "for_loop": { const blockYaml: ForLoopBlockYAML = { ...base, block_type: "for_loop", loop_over_parameter_key: block.loop_over.key, - loop_blocks: convertBlocks(block.loop_blocks), + loop_blocks: convertBlocksToBlockYAML(block.loop_blocks), }; return blockYaml; } @@ -1244,8 +1320,8 @@ function convert(workflow: WorkflowApiResponse): WorkflowCreateYAMLRequest { webhook_callback_url: workflow.webhook_callback_url, totp_verification_url: workflow.totp_verification_url, workflow_definition: { - parameters: convertParameters(userParameters), - blocks: convertBlocks(workflow.workflow_definition.blocks), + parameters: convertParametersToParameterYAML(userParameters), + blocks: convertBlocksToBlockYAML(workflow.workflow_definition.blocks), }, is_saved_task: workflow.is_saved_task, }; diff --git a/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts b/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts index fbdcf935..83b323ce 100644 --- a/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts +++ b/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts @@ -111,7 +111,9 @@ export const WorkflowBlockType = { SendEmail: "send_email", FileURLParser: "file_url_parser", Validation: "validation", -}; + Action: "action", + Navigation: "navigation", +} as const; export type WorkflowBlockType = (typeof WorkflowBlockType)[keyof typeof WorkflowBlockType]; @@ -198,9 +200,36 @@ export type ValidationBlock = WorkflowBlockBase & { parameters: Array; }; -export type ActionBlock = Omit & { +export type ActionBlock = WorkflowBlockBase & { block_type: "action"; + url: string | null; + title: string; + navigation_goal: string | null; + error_code_mapping: Record | null; + max_retries?: number; + max_steps_per_run?: number | null; parameters: Array; + complete_on_download?: boolean; + download_suffix?: string | null; + totp_verification_url?: string | null; + totp_identifier?: string | null; + cache_actions: boolean; +}; + +export type NavigationBlock = WorkflowBlockBase & { + block_type: "navigation"; + url: string | null; + title: string; + navigation_goal: string | null; + error_code_mapping: Record | null; + max_retries?: number; + max_steps_per_run?: number | null; + parameters: Array; + complete_on_download?: boolean; + download_suffix?: string | null; + totp_verification_url?: string | null; + totp_identifier?: string | null; + cache_actions: boolean; }; export type WorkflowBlock = @@ -213,7 +242,8 @@ export type WorkflowBlock = | SendEmailBlock | FileURLParserBlock | ValidationBlock - | ActionBlock; + | ActionBlock + | NavigationBlock; export type WorkflowDefinition = { parameters: Array; diff --git a/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts b/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts index deb440b9..5ac3de8f 100644 --- a/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts +++ b/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts @@ -77,6 +77,7 @@ const BlockTypes = { FILE_URL_PARSER: "file_url_parser", VALIDATION: "validation", ACTION: "action", + NAVIGATION: "navigation", } as const; export type BlockType = (typeof BlockTypes)[keyof typeof BlockTypes]; @@ -91,7 +92,8 @@ export type BlockYAML = | FileUrlParserBlockYAML | ForLoopBlockYAML | ValidationBlockYAML - | ActionBlockYAML; + | ActionBlockYAML + | NavigationBlockYAML; export type BlockYAMLBase = { block_type: BlockType; @@ -139,6 +141,21 @@ export type ActionBlockYAML = BlockYAMLBase & { cache_actions: boolean; }; +export type NavigationBlockYAML = BlockYAMLBase & { + block_type: "navigation"; + url: string | null; + navigation_goal: string | null; + error_code_mapping: Record | null; + max_retries?: number; + max_steps_per_run?: number | null; + parameter_keys?: Array | null; + complete_on_download?: boolean; + download_suffix?: string | null; + totp_verification_url?: string | null; + totp_identifier?: string | null; + cache_actions: boolean; +}; + export type CodeBlockYAML = BlockYAMLBase & { block_type: "code"; code: string;