From fcc83f6fe4285a7693a0aae4e4a4908ea6bd3a29 Mon Sep 17 00:00:00 2001 From: Shuchang Zheng Date: Tue, 26 Nov 2024 07:24:47 -0800 Subject: [PATCH] Add wait block (#1265) --- .../editor/nodes/WaitNode/WaitNode.tsx | 96 +++++++++++++++++++ .../workflows/editor/nodes/WaitNode/types.ts | 19 ++++ .../routes/workflows/editor/nodes/index.ts | 6 +- .../panels/WorkflowNodeLibraryPanel.tsx | 7 ++ .../workflows/editor/workflowEditorUtils.ts | 39 ++++++++ .../routes/workflows/types/workflowTypes.ts | 9 +- .../workflows/types/workflowYamlTypes.ts | 9 +- 7 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 skyvern-frontend/src/routes/workflows/editor/nodes/WaitNode/WaitNode.tsx create mode 100644 skyvern-frontend/src/routes/workflows/editor/nodes/WaitNode/types.ts diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/WaitNode/WaitNode.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/WaitNode/WaitNode.tsx new file mode 100644 index 00000000..7d1f7121 --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/WaitNode/WaitNode.tsx @@ -0,0 +1,96 @@ +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback"; +import { useNodeLabelChangeHandler } from "@/routes/workflows/hooks/useLabelChangeHandler"; +import { StopwatchIcon } from "@radix-ui/react-icons"; +import { Handle, NodeProps, Position, useReactFlow } from "@xyflow/react"; +import { useState } from "react"; +import { EditableNodeTitle } from "../components/EditableNodeTitle"; +import { NodeActionMenu } from "../NodeActionMenu"; +import type { WaitNode } from "./types"; +import { HelpTooltip } from "@/components/HelpTooltip"; + +function WaitNode({ id, data }: NodeProps) { + const { updateNodeData } = useReactFlow(); + const { editable } = data; + const [label, setLabel] = useNodeLabelChangeHandler({ + id, + initialValue: data.label, + }); + const [inputs, setInputs] = useState({ + waitInSeconds: data.waitInSeconds, + }); + const deleteNodeCallback = useDeleteNodeCallback(); + + function handleChange(key: string, value: unknown) { + if (!editable) { + return; + } + setInputs({ ...inputs, [key]: value }); + updateNodeData(id, { [key]: value }); + } + + return ( +
+ + +
+
+
+
+ +
+
+ + Wait Block +
+
+ { + deleteNodeCallback(id); + }} + /> +
+
+
+ + +
+ { + if (!editable) { + return; + } + handleChange("waitInSeconds", Number(event.target.value)); + }} + className="nopan text-xs" + /> +
+
+
+ ); +} + +export { WaitNode }; diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/WaitNode/types.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/WaitNode/types.ts new file mode 100644 index 00000000..2113eb48 --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/WaitNode/types.ts @@ -0,0 +1,19 @@ +import type { Node } from "@xyflow/react"; +import { NodeBaseData } from "../types"; + +export type WaitNodeData = NodeBaseData & { + waitInSeconds: number; +}; + +export type WaitNode = Node; + +export const waitNodeDefaultData: WaitNodeData = { + label: "", + continueOnFailure: false, + editable: true, + waitInSeconds: 0, +}; + +export function isWaitNode(node: Node): node is WaitNode { + return node.type === "wait"; +} diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/index.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/index.ts index 80e73fad..00d4f23b 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/index.ts +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/index.ts @@ -29,6 +29,8 @@ import { ExtractionNode } from "./ExtractionNode/types"; import { ExtractionNode as ExtractionNodeComponent } from "./ExtractionNode/ExtractionNode"; import { LoginNode } from "./LoginNode/types"; import { LoginNode as LoginNodeComponent } from "./LoginNode/LoginNode"; +import { WaitNode } from "./WaitNode/types"; +import { WaitNode as WaitNodeComponent } from "./WaitNode/WaitNode"; export type UtilityNode = StartNode | NodeAdderNode; @@ -45,7 +47,8 @@ export type WorkflowBlockNode = | ActionNode | NavigationNode | ExtractionNode - | LoginNode; + | LoginNode + | WaitNode; export function isUtilityNode(node: AppNode): node is UtilityNode { return node.type === "nodeAdder" || node.type === "start"; @@ -73,4 +76,5 @@ export const nodeTypes = { navigation: memo(NavigationNodeComponent), extraction: memo(ExtractionNodeComponent), login: memo(LoginNodeComponent), + wait: memo(WaitNodeComponent), } 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 bc5c4805..5a41767e 100644 --- a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowNodeLibraryPanel.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowNodeLibraryPanel.tsx @@ -8,6 +8,7 @@ import { ListBulletIcon, LockOpen1Icon, PlusIcon, + StopwatchIcon, UpdateIcon, UploadIcon, } from "@radix-ui/react-icons"; @@ -104,6 +105,12 @@ const nodeLibraryItems: Array<{ title: "Login Block", description: "Login to a website", }, + { + nodeType: "wait", + icon: , + title: "Wait Block", + description: "Wait for some time", + }, ]; type Props = { diff --git a/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts b/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts index ea66e5ad..fb91e458 100644 --- a/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts +++ b/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts @@ -27,6 +27,7 @@ import { WorkflowCreateYAMLRequest, ExtractionBlockYAML, LoginBlockYAML, + WaitBlockYAML, } from "../types/workflowYamlTypes"; import { EMAIL_BLOCK_SENDER, @@ -71,6 +72,7 @@ import { isExtractionNode, } from "./nodes/ExtractionNode/types"; import { loginNodeDefaultData } from "./nodes/LoginNode/types"; +import { waitNodeDefaultData } from "./nodes/WaitNode/types"; export const NEW_NODE_LABEL_PREFIX = "block_"; @@ -273,6 +275,17 @@ function convertToNode( }, }; } + case "wait": { + return { + ...identifiers, + ...common, + type: "wait", + data: { + ...commonData, + waitInSeconds: block.wait_sec ?? 0, + }, + }; + } case "code": { return { ...identifiers, @@ -618,6 +631,17 @@ function createNode( }, }; } + case "wait": { + return { + ...identifiers, + ...common, + type: "wait", + data: { + ...waitNodeDefaultData, + label, + }, + }; + } case "loop": { return { ...identifiers, @@ -832,6 +856,13 @@ function getWorkflowBlock(node: WorkflowBlockNode): BlockYAML { cache_actions: node.data.cacheActions, }; } + case "wait": { + return { + ...base, + block_type: "wait", + wait_sec: node.data.waitInSeconds, + }; + } case "sendEmail": { return { ...base, @@ -1385,6 +1416,14 @@ function convertBlocksToBlockYAML( }; return blockYaml; } + case "wait": { + const blockYaml: WaitBlockYAML = { + ...base, + block_type: "wait", + wait_sec: block.wait_sec, + }; + return blockYaml; + } case "for_loop": { const blockYaml: ForLoopBlockYAML = { ...base, diff --git a/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts b/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts index 9a12a347..443dd32d 100644 --- a/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts +++ b/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts @@ -114,7 +114,8 @@ export type WorkflowBlock = | ActionBlock | NavigationBlock | ExtractionBlock - | LoginBlock; + | LoginBlock + | WaitBlock; export const WorkflowBlockType = { Task: "task", @@ -130,6 +131,7 @@ export const WorkflowBlockType = { Navigation: "navigation", Extraction: "extraction", Login: "login", + Wait: "wait", } as const; export type WorkflowBlockType = @@ -275,6 +277,11 @@ export type LoginBlock = WorkflowBlockBase & { cache_actions: boolean; }; +export type WaitBlock = WorkflowBlockBase & { + block_type: "wait"; + wait_sec?: number; +}; + export type WorkflowDefinition = { parameters: Array; blocks: Array; diff --git a/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts b/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts index c5192848..c0881dfd 100644 --- a/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts +++ b/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts @@ -80,6 +80,7 @@ const BlockTypes = { NAVIGATION: "navigation", EXTRACTION: "extraction", LOGIN: "login", + WAIT: "wait", } as const; export type BlockType = (typeof BlockTypes)[keyof typeof BlockTypes]; @@ -97,7 +98,8 @@ export type BlockYAML = | ActionBlockYAML | NavigationBlockYAML | ExtractionBlockYAML - | LoginBlockYAML; + | LoginBlockYAML + | WaitBlockYAML; export type BlockYAMLBase = { block_type: BlockType; @@ -188,6 +190,11 @@ export type LoginBlockYAML = BlockYAMLBase & { cache_actions: boolean; }; +export type WaitBlockYAML = BlockYAMLBase & { + block_type: "wait"; + wait_sec?: number; +}; + export type CodeBlockYAML = BlockYAMLBase & { block_type: "code"; code: string;