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) {
@@ -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 (
+