From 9fa7c0c96e6449570948de4781c9238acf0c73cb Mon Sep 17 00:00:00 2001 From: Celal Zamanoglu <95054566+celalzamanoglu@users.noreply.github.com> Date: Wed, 10 Dec 2025 00:07:43 +0300 Subject: [PATCH] show metadata for non-navigation blocks on workflow parameters page (#4243) --- .../workflowRun/WorkflowPostRunParameters.tsx | 146 ++++++++++++++++-- .../blockInfo/CodeBlockParameters.tsx | 57 +++++++ .../blockInfo/FileDownloadBlockParameters.tsx | 95 ++++++++++++ .../blockInfo/GotoUrlBlockParameters.tsx | 39 +++++ .../blockInfo/TextPromptBlockParameters.tsx | 86 +++++++++++ 5 files changed, 407 insertions(+), 16 deletions(-) create mode 100644 skyvern-frontend/src/routes/workflows/workflowRun/blockInfo/CodeBlockParameters.tsx create mode 100644 skyvern-frontend/src/routes/workflows/workflowRun/blockInfo/FileDownloadBlockParameters.tsx create mode 100644 skyvern-frontend/src/routes/workflows/workflowRun/blockInfo/GotoUrlBlockParameters.tsx create mode 100644 skyvern-frontend/src/routes/workflows/workflowRun/blockInfo/TextPromptBlockParameters.tsx diff --git a/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowPostRunParameters.tsx b/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowPostRunParameters.tsx index 418bd2c6..1a272186 100644 --- a/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowPostRunParameters.tsx +++ b/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowPostRunParameters.tsx @@ -1,3 +1,4 @@ +import { useMemo } from "react"; import { useWorkflowRunWithWorkflowQuery } from "../hooks/useWorkflowRunWithWorkflowQuery"; import { CodeEditor } from "../components/CodeEditor"; import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea"; @@ -6,12 +7,21 @@ import { useWorkflowRunTimelineQuery } from "../hooks/useWorkflowRunTimelineQuer import { isAction, isWorkflowRunBlock } from "../types/workflowRunTypes"; import { findBlockSurroundingAction } from "./workflowTimelineUtils"; import { TaskBlockParameters } from "./TaskBlockParameters"; -import { isTaskVariantBlock, WorkflowBlockTypes } from "../types/workflowTypes"; +import { + isTaskVariantBlock, + WorkflowBlockTypes, + type WorkflowBlock, + type WorkflowBlockType, +} from "../types/workflowTypes"; import { Input } from "@/components/ui/input"; import { ProxySelector } from "@/components/ProxySelector"; import { SendEmailBlockParameters } from "./blockInfo/SendEmailBlockInfo"; import { ProxyLocation } from "@/api/types"; import { KeyValueInput } from "@/components/KeyValueInput"; +import { CodeBlockParameters } from "./blockInfo/CodeBlockParameters"; +import { TextPromptBlockParameters } from "./blockInfo/TextPromptBlockParameters"; +import { GotoUrlBlockParameters } from "./blockInfo/GotoUrlBlockParameters"; +import { FileDownloadBlockParameters } from "./blockInfo/FileDownloadBlockParameters"; function WorkflowPostRunParameters() { const { data: workflowRunTimeline, isLoading: workflowRunTimelineIsLoading } = @@ -20,14 +30,7 @@ function WorkflowPostRunParameters() { const { data: workflowRun, isLoading: workflowRunIsLoading } = useWorkflowRunWithWorkflowQuery(); const parameters = workflowRun?.parameters ?? {}; - - if (workflowRunIsLoading || workflowRunTimelineIsLoading) { - return
Loading workflow parameters...
; - } - - if (!workflowRun || !workflowRunTimeline) { - return null; - } + const workflow = workflowRun?.workflow; function getActiveBlock() { if (!workflowRunTimeline) { @@ -45,19 +48,37 @@ function WorkflowPostRunParameters() { } const activeBlock = getActiveBlock(); - const isTaskV2 = workflowRun.task_v2 !== null; + const activeBlockLabel = activeBlock?.label ?? null; + const definitionBlock = useMemo(() => { + if (!workflow || !activeBlockLabel) { + return null; + } + return findWorkflowBlockByLabel( + workflow.workflow_definition.blocks, + activeBlockLabel, + ); + }, [workflow, activeBlockLabel]); + const isTaskV2 = Boolean(workflowRun?.task_v2); const webhookCallbackUrl = isTaskV2 - ? workflowRun.task_v2?.webhook_callback_url - : workflowRun.webhook_callback_url; + ? workflowRun?.task_v2?.webhook_callback_url ?? null + : workflowRun?.webhook_callback_url ?? null; const proxyLocation = isTaskV2 - ? workflowRun.task_v2?.proxy_location - : workflowRun.proxy_location; + ? workflowRun?.task_v2?.proxy_location ?? null + : workflowRun?.proxy_location ?? null; const extraHttpHeaders = isTaskV2 - ? workflowRun.task_v2?.extra_http_headers - : workflowRun.extra_http_headers; + ? workflowRun?.task_v2?.extra_http_headers ?? null + : workflowRun?.extra_http_headers ?? null; + + if (workflowRunIsLoading || workflowRunTimelineIsLoading) { + return
Loading workflow parameters...
; + } + + if (!workflowRun || !workflowRunTimeline) { + return null; + } return (
@@ -70,6 +91,27 @@ function WorkflowPostRunParameters() {
) : null} {activeBlock && + activeBlock.block_type === WorkflowBlockTypes.FileDownload && + isBlockOfType(definitionBlock, WorkflowBlockTypes.FileDownload) ? ( +
+
+

File Download Settings

+ +
+
+ ) : null} + {activeBlock && activeBlock.block_type === WorkflowBlockTypes.SendEmail ? (
@@ -105,6 +147,50 @@ function WorkflowPostRunParameters() {
) : null} + {activeBlock && + activeBlock.block_type === WorkflowBlockTypes.Code && + isBlockOfType(definitionBlock, WorkflowBlockTypes.Code) ? ( +
+
+

Code Block

+ +
+
+ ) : null} + {activeBlock && + activeBlock.block_type === WorkflowBlockTypes.TextPrompt && + isBlockOfType(definitionBlock, WorkflowBlockTypes.TextPrompt) ? ( +
+
+

Text Prompt Block

+ +
+
+ ) : null} + {activeBlock && activeBlock.block_type === WorkflowBlockTypes.URL ? ( +
+
+

Go To URL Block

+ +
+
+ ) : null}

Workflow Input Parameters

@@ -192,3 +278,31 @@ function WorkflowPostRunParameters() { } export { WorkflowPostRunParameters }; + +function findWorkflowBlockByLabel( + blocks: Array, + label: string, +): WorkflowBlock | null { + for (const block of blocks) { + if (block.label === label) { + return block; + } + if ( + block.block_type === WorkflowBlockTypes.ForLoop && + block.loop_blocks.length > 0 + ) { + const nested = findWorkflowBlockByLabel(block.loop_blocks, label); + if (nested) { + return nested; + } + } + } + return null; +} + +function isBlockOfType( + block: WorkflowBlock | null, + type: T, +): block is Extract { + return block?.block_type === type; +} diff --git a/skyvern-frontend/src/routes/workflows/workflowRun/blockInfo/CodeBlockParameters.tsx b/skyvern-frontend/src/routes/workflows/workflowRun/blockInfo/CodeBlockParameters.tsx new file mode 100644 index 00000000..1d254982 --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/workflowRun/blockInfo/CodeBlockParameters.tsx @@ -0,0 +1,57 @@ +import { CodeEditor } from "@/routes/workflows/components/CodeEditor"; +import type { WorkflowParameter } from "@/routes/workflows/types/workflowTypes"; + +type Props = { + code: string; + parameters?: Array; +}; + +function CodeBlockParameters({ code, parameters }: Props) { + return ( +
+
+
+

Code

+

+ The Python snippet executed for this block +

+
+ +
+ {parameters && parameters.length > 0 ? ( +
+
+

Parameters

+

+ Inputs passed to this code block +

+
+
+ {parameters.map((parameter) => ( +
+

{parameter.key}

+ {parameter.description ? ( +

+ {parameter.description} +

+ ) : null} +
+ ))} +
+
+ ) : null} +
+ ); +} + +export { CodeBlockParameters }; diff --git a/skyvern-frontend/src/routes/workflows/workflowRun/blockInfo/FileDownloadBlockParameters.tsx b/skyvern-frontend/src/routes/workflows/workflowRun/blockInfo/FileDownloadBlockParameters.tsx new file mode 100644 index 00000000..b7925de3 --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/workflowRun/blockInfo/FileDownloadBlockParameters.tsx @@ -0,0 +1,95 @@ +import { Input } from "@/components/ui/input"; +import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea"; + +type Props = { + prompt?: string | null; + downloadSuffix?: string | null; + downloadTimeout?: number | null; + errorCodeMapping?: Record | null; + maxRetries?: number | null; + maxStepsPerRun?: number | null; +}; + +function FileDownloadBlockParameters({ + prompt, + downloadSuffix, + downloadTimeout, + errorCodeMapping, + maxRetries, + maxStepsPerRun, +}: Props) { + const formattedErrorCodeMapping = errorCodeMapping + ? JSON.stringify(errorCodeMapping, null, 2) + : null; + + return ( +
+ {prompt ? ( +
+
+

Prompt

+

+ Instructions followed to download the file +

+
+ +
+ ) : null} + {downloadSuffix ? ( +
+
+

Download Suffix

+

+ Expected suffix or filename for the downloaded file +

+
+ +
+ ) : null} + {typeof downloadTimeout === "number" ? ( +
+
+

Download Timeout

+

In seconds

+
+ +
+ ) : null} + {typeof maxRetries === "number" ? ( +
+
+

Max Retries

+
+ +
+ ) : null} + {typeof maxStepsPerRun === "number" ? ( +
+
+

Max Steps Per Run

+
+ +
+ ) : null} + {formattedErrorCodeMapping ? ( +
+
+

Error Code Mapping

+
+ +
+ ) : null} + {!downloadSuffix && + typeof downloadTimeout !== "number" && + typeof maxRetries !== "number" && + typeof maxStepsPerRun !== "number" && + !formattedErrorCodeMapping ? ( +
+ No additional download-specific metadata configured for this block. +
+ ) : null} +
+ ); +} + +export { FileDownloadBlockParameters }; diff --git a/skyvern-frontend/src/routes/workflows/workflowRun/blockInfo/GotoUrlBlockParameters.tsx b/skyvern-frontend/src/routes/workflows/workflowRun/blockInfo/GotoUrlBlockParameters.tsx new file mode 100644 index 00000000..28ef528c --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/workflowRun/blockInfo/GotoUrlBlockParameters.tsx @@ -0,0 +1,39 @@ +import { Input } from "@/components/ui/input"; +import { Switch } from "@/components/ui/switch"; + +type Props = { + url: string; + continueOnFailure: boolean; +}; + +function GotoUrlBlockParameters({ url, continueOnFailure }: Props) { + return ( +
+
+
+

URL

+

+ The destination Skyvern navigates to +

+
+ +
+
+
+

Continue on Failure

+

+ Whether to continue if navigation fails +

+
+
+ + + {continueOnFailure ? "Enabled" : "Disabled"} + +
+
+
+ ); +} + +export { GotoUrlBlockParameters }; diff --git a/skyvern-frontend/src/routes/workflows/workflowRun/blockInfo/TextPromptBlockParameters.tsx b/skyvern-frontend/src/routes/workflows/workflowRun/blockInfo/TextPromptBlockParameters.tsx new file mode 100644 index 00000000..6275871d --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/workflowRun/blockInfo/TextPromptBlockParameters.tsx @@ -0,0 +1,86 @@ +import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea"; +import { Input } from "@/components/ui/input"; +import { CodeEditor } from "@/routes/workflows/components/CodeEditor"; +import type { WorkflowParameter } from "@/routes/workflows/types/workflowTypes"; + +type Props = { + prompt: string; + llmKey?: string | null; + jsonSchema?: Record | string | null; + parameters?: Array; +}; + +function TextPromptBlockParameters({ + prompt, + llmKey, + jsonSchema, + parameters, +}: Props) { + return ( +
+
+
+

Prompt

+

+ Instructions passed to the selected LLM +

+
+ +
+ {llmKey ? ( +
+
+

LLM Key

+
+ +
+ ) : null} + {jsonSchema ? ( +
+
+

JSON Schema

+

+ Expected shape of the model response +

+
+ +
+ ) : null} + {parameters && parameters.length > 0 ? ( +
+
+

Parameters

+
+
+ {parameters.map((parameter) => ( +
+

{parameter.key}

+ {parameter.description ? ( +

+ {parameter.description} +

+ ) : null} +
+ ))} +
+
+ ) : null} +
+ ); +} + +export { TextPromptBlockParameters };