From ebe43e12b1aca0cd402a923648a3bb0660f7f314 Mon Sep 17 00:00:00 2001 From: Suchintan Date: Mon, 2 Feb 2026 23:38:34 -0800 Subject: [PATCH] Move Run Workflow button to top of page (#4611) Co-authored-by: Suchintan Singh Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> --- .../src/routes/workflows/RunWorkflowForm.tsx | 97 +++++++++++-------- .../workflows/WorkflowRunParameters.tsx | 55 +++++------ .../panels/WorkflowNodeLibraryPanel.tsx | 2 +- .../routes/workflows/types/workflowTypes.ts | 2 +- .../workflows/types/workflowYamlTypes.ts | 2 +- skyvern/client/types/file_type.py | 2 +- .../skyvern/extract-text-from-image.j2 | 19 ---- skyvern/forge/sdk/workflow/models/block.py | 42 ++------ skyvern/schemas/workflows.py | 1 - 9 files changed, 86 insertions(+), 136 deletions(-) delete mode 100644 skyvern/forge/prompts/skyvern/extract-text-from-image.j2 diff --git a/skyvern-frontend/src/routes/workflows/RunWorkflowForm.tsx b/skyvern-frontend/src/routes/workflows/RunWorkflowForm.tsx index 4bfebb27..a9c8e118 100644 --- a/skyvern-frontend/src/routes/workflows/RunWorkflowForm.tsx +++ b/skyvern-frontend/src/routes/workflows/RunWorkflowForm.tsx @@ -504,6 +504,60 @@ function RunWorkflowForm({ onSubmit={form.handleSubmit(onSubmit, handleInvalid)} className="space-y-8" > +
+
+

+ Parameters{workflow?.title ? ` - ${workflow.title}` : ""} +

+

+ Fill the placeholder values that you have linked throughout your + workflow. +

+
+
+ { + const values = form.getValues(); + const body = getRunWorkflowRequestBody( + values, + workflowParameters, + ); + const transformedBody = transformToWorkflowRunRequest( + body, + workflowPermanentId, + ); + + // Build headers - x-max-steps-override is optional and can be added manually if needed + const headers: Record = { + "Content-Type": "application/json", + "x-api-key": apiCredential ?? "", + }; + + return { + method: "POST", + url: `${runsApiBaseUrl}/run/workflows`, + body: transformedBody, + headers, + } satisfies ApiCommandOptions; + }} + /> + +
+
+ {hasLoginBlockValidationError && ( @@ -1051,49 +1105,6 @@ function RunWorkflowForm({ - -
- { - const values = form.getValues(); - const body = getRunWorkflowRequestBody( - values, - workflowParameters, - ); - const transformedBody = transformToWorkflowRunRequest( - body, - workflowPermanentId, - ); - - // Build headers - x-max-steps-override is optional and can be added manually if needed - const headers: Record = { - "Content-Type": "application/json", - "x-api-key": apiCredential ?? "", - }; - - return { - method: "POST", - url: `${runsApiBaseUrl}/run/workflows`, - body: transformedBody, - headers, - } satisfies ApiCommandOptions; - }} - /> - -
); diff --git a/skyvern-frontend/src/routes/workflows/WorkflowRunParameters.tsx b/skyvern-frontend/src/routes/workflows/WorkflowRunParameters.tsx index 61dd25cf..72660a33 100644 --- a/skyvern-frontend/src/routes/workflows/WorkflowRunParameters.tsx +++ b/skyvern-frontend/src/routes/workflows/WorkflowRunParameters.tsx @@ -44,22 +44,16 @@ function WorkflowRunParameters() { const initialValues = getInitialValues(location, workflowParameters ?? []); - const header = ( -
-

- Parameters{workflow?.title ? ` - ${workflow.title}` : ""} -

-

- Fill the placeholder values that you have linked throughout your - workflow. -

-
- ); - if (isFetching) { return (
- {header} +
+

Parameters

+

+ Fill the placeholder values that you have linked throughout your + workflow. +

+
); @@ -70,26 +64,21 @@ function WorkflowRunParameters() { } return ( -
- {header} - -
+ ); } diff --git a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowNodeLibraryPanel.tsx b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowNodeLibraryPanel.tsx index 992f87e7..99f6cc72 100644 --- a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowNodeLibraryPanel.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowNodeLibraryPanel.tsx @@ -188,7 +188,7 @@ const nodeLibraryItems: Array<{ /> ), title: "File Parser Block", - description: "Parse PDFs, CSVs, Excel files, and Images", + description: "Parse PDFs, CSVs, and Excel files", }, // { // nodeType: "pdfParser", diff --git a/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts b/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts index 0607c235..5b615a94 100644 --- a/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts +++ b/skyvern-frontend/src/routes/workflows/types/workflowTypes.ts @@ -415,7 +415,7 @@ export type SendEmailBlock = WorkflowBlockBase & { export type FileURLParserBlock = WorkflowBlockBase & { block_type: "file_url_parser"; file_url: string; - file_type: "csv" | "excel" | "pdf" | "image"; + file_type: "csv" | "excel" | "pdf"; json_schema: Record | null; }; diff --git a/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts b/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts index fa2ca173..81ced574 100644 --- a/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts +++ b/skyvern-frontend/src/routes/workflows/types/workflowYamlTypes.ts @@ -350,7 +350,7 @@ export type SendEmailBlockYAML = BlockYAMLBase & { export type FileUrlParserBlockYAML = BlockYAMLBase & { block_type: "file_url_parser"; file_url: string; - file_type: "csv" | "excel" | "pdf" | "image"; + file_type: "csv" | "excel" | "pdf"; json_schema?: Record | null; }; diff --git a/skyvern/client/types/file_type.py b/skyvern/client/types/file_type.py index 02decc32..fade3f1d 100644 --- a/skyvern/client/types/file_type.py +++ b/skyvern/client/types/file_type.py @@ -2,4 +2,4 @@ import typing -FileType = typing.Union[typing.Literal["csv", "excel", "pdf", "image"], typing.Any] +FileType = typing.Union[typing.Literal["csv", "excel", "pdf"], typing.Any] diff --git a/skyvern/forge/prompts/skyvern/extract-text-from-image.j2 b/skyvern/forge/prompts/skyvern/extract-text-from-image.j2 deleted file mode 100644 index a577d1e9..00000000 --- a/skyvern/forge/prompts/skyvern/extract-text-from-image.j2 +++ /dev/null @@ -1,19 +0,0 @@ -Extract all visible text from this image. - -MAKE SURE YOU OUTPUT VALID JSON. No text before or after JSON, no trailing commas, no comments, no unnecessary quotes. - -Reply in JSON format with the following keys: -{ - "extracted_text": str // All text extracted from the image -} - -TEXT EXTRACTION GUIDELINES: -- Preserve reading order (top to bottom, left to right) -- For tables: format as rows separated by newlines, columns separated by " | " -- For multi-column layouts: extract each column separately, separated by blank lines -- For forms: format as "Label: Value" on each line -- Preserve line breaks where they appear meaningful (paragraphs, list items) -- Include all visible text: headers, body text, labels, captions, watermarks -- For handwritten text: do your best to transcribe, use [illegible] for unclear parts - -If no text is visible in the image, return an empty string for extracted_text. diff --git a/skyvern/forge/sdk/workflow/models/block.py b/skyvern/forge/sdk/workflow/models/block.py index e128558e..3c1df7d0 100644 --- a/skyvern/forge/sdk/workflow/models/block.py +++ b/skyvern/forge/sdk/workflow/models/block.py @@ -3063,8 +3063,6 @@ class FileParserBlock(Block): return FileType.PDF elif suffix == ".tsv": return FileType.CSV # TSV files are handled by the CSV parser - elif suffix in (".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".tiff", ".tif"): - return FileType.IMAGE else: return FileType.CSV # Default to CSV for .csv and any other extensions @@ -3114,12 +3112,6 @@ class FileParserBlock(Block): validate_pdf_file(file_path, file_identifier=file_url_used) except PDFParsingError as e: raise InvalidFileType(file_url=file_url_used, file_type=self.file_type, error=str(e)) - elif self.file_type == FileType.IMAGE: - kind = filetype.guess(file_path) - if kind is None or not kind.mime.startswith("image/"): - raise InvalidFileType( - file_url=file_url_used, file_type=self.file_type, error="File is not a valid image" - ) async def _parse_csv_file(self, file_path: str) -> list[dict[str, Any]]: """Parse CSV/TSV file and return list of dictionaries.""" @@ -3192,27 +3184,6 @@ class FileParserBlock(Block): except PDFParsingError as e: raise InvalidFileType(file_url=self.file_url, file_type=self.file_type, error=str(e)) - async def _parse_image_file(self, file_path: str) -> str: - """Parse image file using vision LLM for OCR.""" - try: - with open(file_path, "rb") as f: - image_bytes = f.read() - - llm_prompt = prompt_engine.load_prompt("extract-text-from-image") - llm_api_handler = LLMAPIHandlerFactory.get_override_llm_api_handler( - self.override_llm_key, default=app.LLM_API_HANDLER - ) - llm_response = await llm_api_handler( - prompt=llm_prompt, - prompt_name="extract-text-from-image", - screenshots=[image_bytes], - force_dict=True, - ) - return llm_response.get("extracted_text", "") - except Exception: - LOG.exception("Failed to extract text from image via OCR", file_url=self.file_url) - raise - async def _extract_with_ai( self, content: str | list[dict[str, Any]], workflow_run_context: WorkflowRunContext ) -> dict[str, Any]: @@ -3239,8 +3210,9 @@ class FileParserBlock(Block): "extract-information-from-file-text", extracted_text_content=content_str, json_schema=schema_to_use ) - llm_key = self.override_llm_key - llm_api_handler = LLMAPIHandlerFactory.get_override_llm_api_handler(llm_key, default=app.LLM_API_HANDLER) + llm_api_handler = LLMAPIHandlerFactory.get_override_llm_api_handler( + self.override_llm_key, default=app.LLM_API_HANDLER + ) llm_response = await llm_api_handler( prompt=llm_prompt, prompt_name="extract-information-from-file-text", force_dict=False @@ -3289,9 +3261,9 @@ class FileParserBlock(Block): else: file_path = await download_file(self.file_url) - # Auto-detect file type if not explicitly set (IMAGE/EXCEL/PDF are explicit choices) - if self.file_type not in (FileType.IMAGE, FileType.EXCEL, FileType.PDF): - self.file_type = self._detect_file_type_from_url(self.file_url) + # Auto-detect file type based on file extension + detected_file_type = self._detect_file_type_from_url(self.file_url) + self.file_type = detected_file_type # Validate the file type self.validate_file_type(self.file_url, file_path) @@ -3311,8 +3283,6 @@ class FileParserBlock(Block): parsed_data = await self._parse_excel_file(file_path) elif self.file_type == FileType.PDF: parsed_data = await self._parse_pdf_file(file_path) - elif self.file_type == FileType.IMAGE: - parsed_data = await self._parse_image_file(file_path) else: return await self.build_block_result( success=False, diff --git a/skyvern/schemas/workflows.py b/skyvern/schemas/workflows.py index 4b568a17..8f8faf2e 100644 --- a/skyvern/schemas/workflows.py +++ b/skyvern/schemas/workflows.py @@ -67,7 +67,6 @@ class FileType(StrEnum): CSV = "csv" EXCEL = "excel" PDF = "pdf" - IMAGE = "image" class PDFFormat(StrEnum):