diff --git a/skyvern-frontend/src/routes/workflows/WorkflowRun.tsx b/skyvern-frontend/src/routes/workflows/WorkflowRun.tsx index e2c87503..735ed45e 100644 --- a/skyvern-frontend/src/routes/workflows/WorkflowRun.tsx +++ b/skyvern-frontend/src/routes/workflows/WorkflowRun.tsx @@ -5,6 +5,8 @@ import { WorkflowRunStatusApiResponse, } from "@/api/types"; import { StatusBadge } from "@/components/StatusBadge"; +import { ZoomableImage } from "@/components/ZoomableImage"; +import { AspectRatio } from "@/components/ui/aspect-ratio"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -25,10 +27,22 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import { toast } from "@/components/ui/use-toast"; +import { useApiCredential } from "@/hooks/useApiCredential"; import { useCredentialGetter } from "@/hooks/useCredentialGetter"; -import { basicTimeFormat } from "@/util/timeFormat"; +import { copyText } from "@/util/copyText"; +import { apiBaseUrl, envCredential } from "@/util/env"; +import { basicTimeFormat, timeFormatWithShortDate } from "@/util/timeFormat"; import { cn } from "@/util/utils"; +import { + CopyIcon, + Pencil2Icon, + PlayIcon, + ReaderIcon, +} from "@radix-ui/react-icons"; import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import fetchToCurl from "fetch-to-curl"; +import { useEffect, useState } from "react"; import { Link, useNavigate, @@ -37,16 +51,9 @@ import { } from "react-router-dom"; import { TaskActions } from "../tasks/list/TaskActions"; import { TaskListSkeletonRows } from "../tasks/list/TaskListSkeletonRows"; -import { useEffect, useState } from "react"; import { statusIsNotFinalized, statusIsRunningOrQueued } from "../tasks/types"; -import { apiBaseUrl, envCredential } from "@/util/env"; -import { toast } from "@/components/ui/use-toast"; -import { CopyIcon, Pencil2Icon, PlayIcon } from "@radix-ui/react-icons"; +import { CodeEditor } from "./components/CodeEditor"; import { useWorkflowQuery } from "./hooks/useWorkflowQuery"; -import fetchToCurl from "fetch-to-curl"; -import { useApiCredential } from "@/hooks/useApiCredential"; -import { copyText } from "@/util/copyText"; -import { ZoomableImage } from "@/components/ZoomableImage"; type StreamMessage = { task_id: string; @@ -112,8 +119,13 @@ function WorkflowRun() { }, placeholderData: keepPreviousData, refetchOnMount: workflowRun?.status === Status.Running, + refetchOnWindowFocus: workflowRun?.status === Status.Running, }); + const currentRunningTask = workflowTasks?.find( + (task) => task.status === Status.Running, + ); + const workflowRunIsRunningOrQueued = workflowRun && statusIsRunningOrQueued(workflowRun); @@ -189,7 +201,7 @@ function WorkflowRun() { function getStream() { if (workflowRun?.status === Status.Created) { return ( -
+
Workflow has been created. Stream will start when the workflow is running.
@@ -197,7 +209,7 @@ function WorkflowRun() { } if (workflowRun?.status === Status.Queued) { return ( -
+
Your workflow run is queued. Stream will start when the workflow is running.
@@ -206,7 +218,7 @@ function WorkflowRun() { if (workflowRun?.status === Status.Running && streamImgSrc.length === 0) { return ( -
+
Starting the stream...
); @@ -215,7 +227,10 @@ function WorkflowRun() { if (workflowRun?.status === Status.Running && streamImgSrc.length > 0) { return (
- +
); } @@ -240,7 +255,7 @@ function WorkflowRun() {
-
+

{workflowRunId}

{workflowRunIsLoading ? ( @@ -308,20 +323,72 @@ function WorkflowRun() {
- {getStream()} -
+ {workflowRun && statusIsNotFinalized(workflowRun) && ( +
+
+ {getStream()} +
+
+
Current Task
+ {workflowRunIsLoading || !currentRunningTask ? ( +
Waiting for a task to start...
+ ) : ( +
+
+ + {currentRunningTask.task_id} +
+
+ + + {currentRunningTask.request.url} + +
+
+ + + + +
+
+ + + {currentRunningTask && + timeFormatWithShortDate(currentRunningTask.created_at)} + +
+
+ +
+
+ )} +
+
+ )} +
-

Tasks

+

+ {workflowRunIsRunningOrQueued ? "Previous Blocks" : "Blocks"} +

- + - ID - URL - Status - Created At - + + ID + + URL + Status + + Created At + + @@ -332,39 +399,51 @@ function WorkflowRun() { No tasks ) : ( - workflowTasks?.map((task) => { - return ( - - handleNavigate(event, task.task_id)} - > - {task.task_id} - - handleNavigate(event, task.task_id)} - > - {task.request.url} - - handleNavigate(event, task.task_id)} - > - - - handleNavigate(event, task.task_id)} - > - {basicTimeFormat(task.created_at)} - - - - - - ); - }) + workflowTasks + ?.filter( + (task) => task.task_id !== currentRunningTask?.task_id, + ) + .map((task) => { + return ( + + + handleNavigate(event, task.task_id) + } + > + {task.task_id} + + + handleNavigate(event, task.task_id) + } + > + {task.request.url} + + + handleNavigate(event, task.task_id) + } + > + + + + handleNavigate(event, task.task_id) + } + > + {basicTimeFormat(task.created_at)} + + + + + + ); + }) )}
@@ -399,23 +478,32 @@ function WorkflowRun() {
-
-
-

Parameters

-
- {Object.entries(parameters).map(([key, value]) => { - return ( -
- - {typeof value === "string" ? ( - - ) : ( - - )} -
- ); - })} -
+ {Object.entries(parameters).length > 0 && ( +
+
+

Parameters

+
+ {Object.entries(parameters).map(([key, value]) => { + return ( +
+ + {typeof value === "string" ? ( + + ) : ( + + )} +
+ ); + })} +
+ )}
); } diff --git a/skyvern-frontend/src/routes/workflows/components/CodeEditor.tsx b/skyvern-frontend/src/routes/workflows/components/CodeEditor.tsx index 1b4ffe2a..f2050e6b 100644 --- a/skyvern-frontend/src/routes/workflows/components/CodeEditor.tsx +++ b/skyvern-frontend/src/routes/workflows/components/CodeEditor.tsx @@ -6,7 +6,7 @@ import { cn } from "@/util/utils"; type Props = { value: string; - onChange: (value: string) => void; + onChange?: (value: string) => void; language: "python" | "json"; disabled?: boolean; minHeight?: string; diff --git a/skyvern-frontend/src/util/timeFormat.ts b/skyvern-frontend/src/util/timeFormat.ts index 051480a7..57ad206b 100644 --- a/skyvern-frontend/src/util/timeFormat.ts +++ b/skyvern-frontend/src/util/timeFormat.ts @@ -10,4 +10,12 @@ function basicTimeFormat(time: string): string { return `${dateString} at ${timeString}`; } -export { basicTimeFormat }; +function timeFormatWithShortDate(time: string): string { + const date = new Date(time); + const dateString = + date.getMonth() + 1 + "/" + date.getDate() + "/" + date.getFullYear(); + const timeString = date.toLocaleTimeString("en-US"); + return `${dateString} at ${timeString}`; +} + +export { basicTimeFormat, timeFormatWithShortDate };