diff --git a/skyvern-frontend/src/api/types.ts b/skyvern-frontend/src/api/types.ts index 7a9236405b..07479917ec 100644 --- a/skyvern-frontend/src/api/types.ts +++ b/skyvern-frontend/src/api/types.ts @@ -37,7 +37,6 @@ export const ProxyLocation = { ResidentialJP: "RESIDENTIAL_JP", ResidentialGB: "RESIDENTIAL_GB", ResidentialFR: "RESIDENTIAL_FR", - ResidentialDE: "RESIDENTIAL_DE", None: "NONE", } as const; diff --git a/skyvern-frontend/src/components/ProxySelector.tsx b/skyvern-frontend/src/components/ProxySelector.tsx index a55840b064..dfe1b05d79 100644 --- a/skyvern-frontend/src/components/ProxySelector.tsx +++ b/skyvern-frontend/src/components/ProxySelector.tsx @@ -39,9 +39,6 @@ function ProxySelector({ value, onChange, className }: Props) { Residential (France) - - Residential (Germany) - ); diff --git a/skyvern-frontend/src/routes/workflows/editor/helpContent.ts b/skyvern-frontend/src/routes/workflows/editor/helpContent.ts index d447fbc813..7b7f5e54d3 100644 --- a/skyvern-frontend/src/routes/workflows/editor/helpContent.ts +++ b/skyvern-frontend/src/routes/workflows/editor/helpContent.ts @@ -39,6 +39,11 @@ export const basePlaceholderContent = { export const helpTooltips = { task: baseHelpTooltipContent, + taskv2: { + ...baseHelpTooltipContent, + maxIterations: + "The maximum number of iterations this task will take to achieve its goal.", + }, navigation: baseHelpTooltipContent, extraction: { ...baseHelpTooltipContent, @@ -100,6 +105,10 @@ export const helpTooltips = { export const placeholders = { task: basePlaceholderContent, + taskv2: { + ...basePlaceholderContent, + prompt: "Tell Skyvern what to do", + }, navigation: { ...basePlaceholderContent, navigationGoal: diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/Taskv2Node/Taskv2Node.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/Taskv2Node/Taskv2Node.tsx new file mode 100644 index 0000000000..d403396e7d --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/Taskv2Node/Taskv2Node.tsx @@ -0,0 +1,179 @@ +import { HelpTooltip } from "@/components/HelpTooltip"; +import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { Input } from "@/components/ui/input"; +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 { WorkflowBlockTypes } from "@/routes/workflows/types/workflowTypes"; +import { Handle, NodeProps, Position, useReactFlow } from "@xyflow/react"; +import { useState } from "react"; +import { helpTooltips, placeholders } from "../../helpContent"; +import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow"; +import { NodeActionMenu } from "../NodeActionMenu"; +import { WorkflowBlockIcon } from "../WorkflowBlockIcon"; +import { EditableNodeTitle } from "../components/EditableNodeTitle"; +import { MAX_ITERATIONS_DEFAULT, type Taskv2Node } from "./types"; + +function Taskv2Node({ id, data, type }: NodeProps) { + const { updateNodeData } = useReactFlow(); + const { editable } = data; + const deleteNodeCallback = useDeleteNodeCallback(); + const [label, setLabel] = useNodeLabelChangeHandler({ + id, + initialValue: data.label, + }); + + const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id }); + + const [inputs, setInputs] = useState({ + prompt: data.prompt, + url: data.url, + totpVerificationUrl: data.totpVerificationUrl, + totpIdentifier: data.totpIdentifier, + maxIterations: data.maxIterations, + }); + + function handleChange(key: string, value: unknown) { + if (!editable) { + return; + } + setInputs({ ...inputs, [key]: value }); + updateNodeData(id, { [key]: value }); + } + + return ( +
+ + +
+
+
+
+ +
+
+ + Task v2 Block +
+
+ { + deleteNodeCallback(id); + }} + /> +
+
+
+
+ + {isFirstWorkflowBlock ? ( +
+ Tip: Use the {"+"} button to add parameters! +
+ ) : null} +
+ { + handleChange("prompt", value); + }} + value={inputs.prompt} + placeholder={placeholders[type]["prompt"]} + className="nopan text-xs" + /> +
+
+ + { + handleChange("url", value); + }} + value={inputs.url} + placeholder={placeholders[type]["url"]} + className="nopan text-xs" + /> +
+
+ + + + + Advanced Settings + + +
+
+
+ + +
+ { + handleChange("maxIterations", Number(event.target.value)); + }} + /> +
+
+
+ + +
+ { + handleChange("totpIdentifier", value); + }} + value={inputs.totpIdentifier ?? ""} + placeholder={placeholders["navigation"]["totpIdentifier"]} + className="nopan text-xs" + /> +
+
+
+
+
+
+
+ ); +} + +export { Taskv2Node }; diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/Taskv2Node/types.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/Taskv2Node/types.ts new file mode 100644 index 0000000000..5175b0470f --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/Taskv2Node/types.ts @@ -0,0 +1,29 @@ +import { Node } from "@xyflow/react"; +import { NodeBaseData } from "../types"; + +export const MAX_ITERATIONS_DEFAULT = 10; + +export type Taskv2NodeData = NodeBaseData & { + prompt: string; + url: string; + totpVerificationUrl: string | null; + totpIdentifier: string | null; + maxIterations: number | null; +}; + +export type Taskv2Node = Node; + +export const taskv2NodeDefaultData: Taskv2NodeData = { + label: "", + continueOnFailure: false, + editable: true, + prompt: "", + url: "", + totpIdentifier: null, + totpVerificationUrl: null, + maxIterations: 10, +}; + +export function isTaskV2Node(node: Node): node is Taskv2Node { + return node.type === "taskv2"; +} diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/WorkflowBlockIcon.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/WorkflowBlockIcon.tsx index 244a71fc2b..735bafaa48 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/WorkflowBlockIcon.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/WorkflowBlockIcon.tsx @@ -53,7 +53,8 @@ function WorkflowBlockIcon({ workflowBlockType, className }: Props) { case "send_email": { return ; } - case "task": { + case "task": + case "task_v2": { return ; } case "text_prompt": { diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/index.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/index.ts index 8dbb587e4d..9228e69ce0 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/index.ts +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/index.ts @@ -35,6 +35,8 @@ import { FileDownloadNode } from "./FileDownloadNode/types"; import { FileDownloadNode as FileDownloadNodeComponent } from "./FileDownloadNode/FileDownloadNode"; import { PDFParserNode } from "./PDFParserNode/types"; import { PDFParserNode as PDFParserNodeComponent } from "./PDFParserNode/PDFParserNode"; +import { Taskv2Node } from "./Taskv2Node/types"; +import { Taskv2Node as Taskv2NodeComponent } from "./Taskv2Node/Taskv2Node"; export type UtilityNode = StartNode | NodeAdderNode; @@ -54,7 +56,8 @@ export type WorkflowBlockNode = | LoginNode | WaitNode | FileDownloadNode - | PDFParserNode; + | PDFParserNode + | Taskv2Node; export function isUtilityNode(node: AppNode): node is UtilityNode { return node.type === "nodeAdder" || node.type === "start"; @@ -85,4 +88,5 @@ export const nodeTypes = { wait: memo(WaitNodeComponent), fileDownload: memo(FileDownloadNodeComponent), pdfParser: memo(PDFParserNodeComponent), + taskv2: memo(Taskv2NodeComponent), } as const; diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/types.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/types.ts index 6c1434076e..34161db126 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/types.ts +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/types.ts @@ -46,4 +46,5 @@ export const workflowBlockTitle: { validation: "Validation", wait: "Wait", pdf_parser: "PDF Parser", + task_v2: "Task v2", }; diff --git a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowNodeLibraryPanel.tsx b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowNodeLibraryPanel.tsx index c2fc441196..9bccac3417 100644 --- a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowNodeLibraryPanel.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowNodeLibraryPanel.tsx @@ -67,6 +67,17 @@ const nodeLibraryItems: Array<{ title: "Task Block", description: "Takes actions or extracts information", }, + { + nodeType: "taskv2", + icon: ( + + ), + title: "Task v2 Block", + description: "Runs a Skyvern v2 Task", + }, { nodeType: "textPrompt", icon: ( diff --git a/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts b/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts index f215b7d4b4..7d36ae8f1d 100644 --- a/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts +++ b/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts @@ -33,6 +33,7 @@ import { WaitBlockYAML, FileDownloadBlockYAML, PDFParserBlockYAML, + Taskv2BlockYAML, } from "../types/workflowYamlTypes"; import { EMAIL_BLOCK_SENDER, @@ -86,6 +87,7 @@ import { isWaitNode, waitNodeDefaultData } from "./nodes/WaitNode/types"; import { fileDownloadNodeDefaultData } from "./nodes/FileDownloadNode/types"; import { ProxyLocation } from "@/api/types"; import { pdfParserNodeDefaultData } from "./nodes/PDFParserNode/types"; +import { taskv2NodeDefaultData } from "./nodes/Taskv2Node/types"; export const NEW_NODE_LABEL_PREFIX = "block_"; @@ -199,6 +201,21 @@ function convertToNode( }, }; } + case "task_v2": { + return { + ...identifiers, + ...common, + type: "taskv2", + data: { + ...commonData, + prompt: block.prompt, + url: block.url ?? "", + maxIterations: block.max_iterations, + totpIdentifier: block.totp_identifier, + totpVerificationUrl: block.totp_verification_url, + }, + }; + } case "validation": { return { ...identifiers, @@ -650,6 +667,17 @@ function createNode( }, }; } + case "taskv2": { + return { + ...identifiers, + ...common, + type: "taskv2", + data: { + ...taskv2NodeDefaultData, + label, + }, + }; + } case "validation": { return { ...identifiers, @@ -859,6 +887,17 @@ function getWorkflowBlock(node: WorkflowBlockNode): BlockYAML { cache_actions: node.data.cacheActions, }; } + case "taskv2": { + return { + ...base, + block_type: "task_v2", + prompt: node.data.prompt, + max_iterations: node.data.maxIterations, + totp_identifier: node.data.totpIdentifier, + totp_verification_url: node.data.totpVerificationUrl, + url: node.data.url, + }; + } case "validation": { return { ...base, @@ -1513,6 +1552,18 @@ function convertBlocksToBlockYAML( }; return blockYaml; } + case "task_v2": { + const blockYaml: Taskv2BlockYAML = { + ...base, + block_type: "task_v2", + prompt: block.prompt, + url: block.url, + max_iterations: block.max_iterations, + totp_identifier: block.totp_identifier, + totp_verification_url: block.totp_verification_url, + }; + return blockYaml; + } case "validation": { const blockYaml: ValidationBlockYAML = { ...base, diff --git a/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts b/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts index 75ea762de6..577a5a0110 100644 --- a/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts +++ b/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts @@ -155,7 +155,8 @@ export type WorkflowBlock = | LoginBlock | WaitBlock | FileDownloadBlock - | PDFParserBlock; + | PDFParserBlock + | Taskv2Block; export const WorkflowBlockTypes = { Task: "task", @@ -174,6 +175,7 @@ export const WorkflowBlockTypes = { Wait: "wait", FileDownload: "file_download", PDFParser: "pdf_parser", + Taskv2: "task_v2", } as const; export function isTaskVariantBlock(item: { @@ -231,6 +233,15 @@ export type TaskBlock = WorkflowBlockBase & { cache_actions: boolean; }; +export type Taskv2Block = WorkflowBlockBase & { + block_type: "task_v2"; + prompt: string; + url: string | null; + totp_verification_url: string | null; + totp_identifier: string | null; + max_iterations: number | null; +}; + export type ForLoopBlock = WorkflowBlockBase & { block_type: "for_loop"; loop_over: WorkflowParameter; diff --git a/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts b/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts index 8f52728147..339c81bb90 100644 --- a/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts +++ b/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts @@ -98,7 +98,8 @@ export type BlockYAML = | LoginBlockYAML | WaitBlockYAML | FileDownloadBlockYAML - | PDFParserBlockYAML; + | PDFParserBlockYAML + | Taskv2BlockYAML; export type BlockYAMLBase = { block_type: WorkflowBlockType; @@ -126,6 +127,15 @@ export type TaskBlockYAML = BlockYAMLBase & { terminate_criterion: string | null; }; +export type Taskv2BlockYAML = BlockYAMLBase & { + block_type: "task_v2"; + url: string | null; + prompt: string; + totp_verification_url: string | null; + totp_identifier: string | null; + max_iterations: number | null; +}; + export type ValidationBlockYAML = BlockYAMLBase & { block_type: "validation"; complete_criterion: string | null;