import { getClient } from "@/api/AxiosClient"; import { Status, TaskApiResponse, 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"; import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, } from "@/components/ui/pagination"; import { Skeleton } from "@/components/ui/skeleton"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { toast } from "@/components/ui/use-toast"; import { useApiCredential } from "@/hooks/useApiCredential"; import { useCredentialGetter } from "@/hooks/useCredentialGetter"; 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, useParams, useSearchParams, } from "react-router-dom"; import { TaskActions } from "../tasks/list/TaskActions"; import { TaskListSkeletonRows } from "../tasks/list/TaskListSkeletonRows"; import { statusIsNotFinalized, statusIsRunningOrQueued } from "../tasks/types"; import { CodeEditor } from "./components/CodeEditor"; import { useWorkflowQuery } from "./hooks/useWorkflowQuery"; type StreamMessage = { task_id: string; status: string; screenshot?: string; }; let socket: WebSocket | null = null; const wssBaseUrl = import.meta.env.VITE_WSS_BASE_URL; function WorkflowRun() { const [searchParams, setSearchParams] = useSearchParams(); const page = searchParams.get("page") ? Number(searchParams.get("page")) : 1; const { workflowRunId, workflowPermanentId } = useParams(); const credentialGetter = useCredentialGetter(); const [streamImgSrc, setStreamImgSrc] = useState(""); const navigate = useNavigate(); const apiCredential = useApiCredential(); const { data: workflow, isLoading: workflowIsLoading } = useWorkflowQuery({ workflowPermanentId, }); const { data: workflowRun, isLoading: workflowRunIsLoading } = useQuery({ queryKey: ["workflowRun", workflowPermanentId, workflowRunId], queryFn: async () => { const client = await getClient(credentialGetter); return client .get(`/workflows/${workflowPermanentId}/runs/${workflowRunId}`) .then((response) => response.data); }, refetchInterval: (query) => { if (!query.state.data) { return false; } if (statusIsNotFinalized(query.state.data)) { return 5000; } return false; }, placeholderData: keepPreviousData, refetchOnMount: (query) => { if (!query.state.data) { return false; } return statusIsRunningOrQueued(query.state.data); }, refetchOnWindowFocus: (query) => { if (!query.state.data) { return false; } return statusIsRunningOrQueued(query.state.data); }, }); const { data: workflowTasks, isLoading: workflowTasksIsLoading } = useQuery< Array >({ queryKey: ["workflowTasks", workflowRunId, page], queryFn: async () => { const client = await getClient(credentialGetter); const params = new URLSearchParams(); params.append("page", String(page)); return client .get(`/tasks?workflow_run_id=${workflowRunId}`, { params }) .then((response) => response.data); }, refetchInterval: () => { if (workflowRun?.status === Status.Running) { return 5000; } return false; }, 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); useEffect(() => { if (!workflowRunIsRunningOrQueued) { return; } async function run() { // Create WebSocket connection. let credential = null; if (credentialGetter) { const token = await credentialGetter(); credential = `?token=Bearer ${token}`; } else { credential = `?apikey=${envCredential}`; } if (socket) { socket.close(); } socket = new WebSocket( `${wssBaseUrl}/stream/workflow_runs/${workflowRunId}${credential}`, ); // Listen for messages socket.addEventListener("message", (event) => { try { const message: StreamMessage = JSON.parse(event.data); if (message.screenshot) { setStreamImgSrc(message.screenshot); } if ( message.status === "completed" || message.status === "failed" || message.status === "terminated" ) { socket?.close(); if ( message.status === "failed" || message.status === "terminated" ) { toast({ title: "Run Failed", description: "The workflow run has failed.", variant: "destructive", }); } else if (message.status === "completed") { toast({ title: "Run Completed", description: "The workflow run has been completed.", variant: "success", }); } } } catch (e) { console.error("Failed to parse message", e); } }); socket.addEventListener("close", () => { socket = null; }); } run(); return () => { if (socket) { socket.close(); socket = null; } }; }, [credentialGetter, workflowRunId, workflowRunIsRunningOrQueued]); function getStream() { if (workflowRun?.status === Status.Created) { return (
Workflow has been created. Stream will start when the workflow is running.
); } if (workflowRun?.status === Status.Queued) { return (
Your workflow run is queued. Stream will start when the workflow is running.
); } if (workflowRun?.status === Status.Running && streamImgSrc.length === 0) { return (
Starting the stream...
); } if (workflowRun?.status === Status.Running && streamImgSrc.length > 0) { return (
); } return null; } function handleNavigate(event: React.MouseEvent, id: string) { if (event.ctrlKey || event.metaKey) { window.open( window.location.origin + `/tasks/${id}/actions`, "_blank", "noopener,noreferrer", ); } else { navigate(`/tasks/${id}/actions`); } } const parameters = workflowRun?.parameters ?? {}; return (

{workflowRunId}

{workflowRunIsLoading ? ( ) : workflowRun ? ( ) : null}

{workflowIsLoading ? ( ) : ( workflow?.title )}

{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)}
)}
)}

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

ID URL Status Created At {workflowTasksIsLoading ? ( ) : workflowTasks?.length === 0 ? ( No tasks ) : ( 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)} ); }) )}
{ if (page === 1) { return; } const params = new URLSearchParams(); params.set("page", String(Math.max(1, page - 1))); setSearchParams(params, { replace: true }); }} /> {page} { const params = new URLSearchParams(); params.set("page", String(page + 1)); setSearchParams(params, { replace: true }); }} />
{Object.entries(parameters).length > 0 && (

Input Parameter Values

{Object.entries(parameters).length === 0 && (
This workflow doesn't have any input parameters.
)} {Object.entries(parameters).map(([key, value]) => { return (
{typeof value === "string" ? ( ) : ( )}
); })}
)}
); } export { WorkflowRun };