Workflow Run Timeline UI (#1433)
This commit is contained in:
@@ -19,7 +19,6 @@ import {
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||
import { observerFeatureEnabled } from "@/util/env";
|
||||
import {
|
||||
FileTextIcon,
|
||||
GearIcon,
|
||||
@@ -233,35 +232,33 @@ function PromptBox() {
|
||||
placeholder="Enter your prompt..."
|
||||
rows={1}
|
||||
/>
|
||||
{observerFeatureEnabled && (
|
||||
<Select value={selectValue} onValueChange={setSelectValue}>
|
||||
<SelectTrigger className="w-48 focus:ring-0">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="border-slate-500 bg-slate-elevation3">
|
||||
<CustomSelectItem value="v1">
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<SelectItemText>Skyvern 1.0 (Tasks)</SelectItemText>
|
||||
</div>
|
||||
<div className="text-xs text-slate-400">
|
||||
best for simple tasks
|
||||
</div>
|
||||
<Select value={selectValue} onValueChange={setSelectValue}>
|
||||
<SelectTrigger className="w-48 focus:ring-0">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="border-slate-500 bg-slate-elevation3">
|
||||
<CustomSelectItem value="v1">
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<SelectItemText>Skyvern 1.0 (Tasks)</SelectItemText>
|
||||
</div>
|
||||
</CustomSelectItem>
|
||||
<CustomSelectItem value="v2" className="hover:bg-slate-800">
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<SelectItemText>Skyvern 2.0 (Observer)</SelectItemText>
|
||||
</div>
|
||||
<div className="text-xs text-slate-400">
|
||||
best for complex tasks
|
||||
</div>
|
||||
<div className="text-xs text-slate-400">
|
||||
best for simple tasks
|
||||
</div>
|
||||
</CustomSelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</div>
|
||||
</CustomSelectItem>
|
||||
<CustomSelectItem value="v2" className="hover:bg-slate-800">
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<SelectItemText>Skyvern 2.0 (Observer)</SelectItemText>
|
||||
</div>
|
||||
<div className="text-xs text-slate-400">
|
||||
best for complex tasks
|
||||
</div>
|
||||
</div>
|
||||
</CustomSelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="flex items-center">
|
||||
{startObserverCruiseMutation.isPending ||
|
||||
getTaskFromPromptMutation.isPending ||
|
||||
@@ -271,7 +268,7 @@ function PromptBox() {
|
||||
<PaperPlaneIcon
|
||||
className="h-6 w-6 cursor-pointer"
|
||||
onClick={async () => {
|
||||
if (observerFeatureEnabled && selectValue === "v2") {
|
||||
if (selectValue === "v2") {
|
||||
startObserverCruiseMutation.mutate(prompt);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { ArtifactApiResponse, ArtifactType, Status } from "@/api/types";
|
||||
import { ZoomableImage } from "@/components/ZoomableImage";
|
||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { getImageURL } from "./artifactUtils";
|
||||
import { ReloadIcon } from "@radix-ui/react-icons";
|
||||
import { statusIsNotFinalized } from "../types";
|
||||
@@ -15,15 +14,18 @@ type Props = {
|
||||
};
|
||||
|
||||
function ActionScreenshot({ stepId, index, taskStatus }: Props) {
|
||||
const { taskId } = useParams();
|
||||
const credentialGetter = useCredentialGetter();
|
||||
|
||||
const { data: artifacts, isLoading } = useQuery<Array<ArtifactApiResponse>>({
|
||||
queryKey: ["task", taskId, "steps", stepId, "artifacts"],
|
||||
const {
|
||||
data: artifacts,
|
||||
isLoading,
|
||||
isFetching,
|
||||
} = useQuery<Array<ArtifactApiResponse>>({
|
||||
queryKey: ["step", stepId, "artifacts"],
|
||||
queryFn: async () => {
|
||||
const client = await getClient(credentialGetter);
|
||||
return client
|
||||
.get(`/tasks/${taskId}/steps/${stepId}/artifacts`)
|
||||
.get(`/step/${stepId}/artifacts`)
|
||||
.then((response) => response.data);
|
||||
},
|
||||
refetchInterval: (query) => {
|
||||
@@ -46,7 +48,7 @@ function ActionScreenshot({ stepId, index, taskStatus }: Props) {
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="mx-auto flex max-h-[400px] flex-col items-center gap-2 overflow-hidden">
|
||||
<div className="flex h-full items-center justify-center gap-2 bg-slate-elevation1">
|
||||
<ReloadIcon className="h-6 w-6 animate-spin" />
|
||||
<div>Loading screenshot...</div>
|
||||
</div>
|
||||
@@ -59,14 +61,25 @@ function ActionScreenshot({ stepId, index, taskStatus }: Props) {
|
||||
statusIsNotFinalized({ status: taskStatus })
|
||||
) {
|
||||
return <div>The screenshot for this action is not available yet.</div>;
|
||||
} else if (isFetching) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center gap-2 bg-slate-elevation1">
|
||||
<ReloadIcon className="h-6 w-6 animate-spin" />
|
||||
<div>Loading screenshot...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!screenshot) {
|
||||
return <div>No screenshot found for this action.</div>;
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center bg-slate-elevation1">
|
||||
No screenshot found for this action.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<figure className="mx-auto flex max-w-full flex-col items-center gap-2 overflow-hidden">
|
||||
<figure className="mx-auto flex max-w-full flex-col items-center gap-2 overflow-hidden rounded">
|
||||
<ZoomableImage src={getImageURL(screenshot)} alt="llm-screenshot" />
|
||||
</figure>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
TaskApiResponse,
|
||||
WorkflowRunStatusApiResponse,
|
||||
} from "@/api/types";
|
||||
import { StatusBadge } from "@/components/StatusBadge";
|
||||
import { SwitchBarNavigation } from "@/components/SwitchBarNavigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -18,20 +20,18 @@ import {
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { useApiCredential } from "@/hooks/useApiCredential";
|
||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||
import { cn } from "@/util/utils";
|
||||
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
||||
import { WorkflowApiResponse } from "@/routes/workflows/types/workflowTypes";
|
||||
import { copyText } from "@/util/copyText";
|
||||
import { apiBaseUrl } from "@/util/env";
|
||||
import { CopyIcon, PlayIcon, ReloadIcon } from "@radix-ui/react-icons";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { Link, NavLink, Outlet, useParams } from "react-router-dom";
|
||||
import { useTaskQuery } from "./hooks/useTaskQuery";
|
||||
import fetchToCurl from "fetch-to-curl";
|
||||
import { apiBaseUrl } from "@/util/env";
|
||||
import { useApiCredential } from "@/hooks/useApiCredential";
|
||||
import { copyText } from "@/util/copyText";
|
||||
import { WorkflowApiResponse } from "@/routes/workflows/types/workflowTypes";
|
||||
import { StatusBadge } from "@/components/StatusBadge";
|
||||
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
||||
import { Link, Outlet, useParams } from "react-router-dom";
|
||||
import { statusIsFinalized } from "../types";
|
||||
import { useTaskQuery } from "./hooks/useTaskQuery";
|
||||
|
||||
function createTaskRequestObject(values: TaskApiResponse) {
|
||||
return {
|
||||
@@ -257,7 +257,7 @@ function TaskDetails() {
|
||||
workflow &&
|
||||
workflowRun && (
|
||||
<Link
|
||||
to={`/workflows/${workflow.workflow_permanent_id}/${workflowRun.workflow_run_id}/blocks`}
|
||||
to={`/workflows/${workflow.workflow_permanent_id}/${workflowRun.workflow_run_id}/overview`}
|
||||
>
|
||||
{workflow.title}
|
||||
</Link>
|
||||
@@ -274,64 +274,26 @@ function TaskDetails() {
|
||||
{failureReason}
|
||||
</>
|
||||
)}
|
||||
<div className="flex w-fit gap-2 rounded-sm border border-slate-700 p-2">
|
||||
<NavLink
|
||||
to="actions"
|
||||
replace
|
||||
className={({ isActive }) => {
|
||||
return cn(
|
||||
"cursor-pointer rounded-sm px-3 py-2 hover:bg-slate-700",
|
||||
{
|
||||
"bg-slate-700": isActive,
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
Actions
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="recording"
|
||||
replace
|
||||
className={({ isActive }) => {
|
||||
return cn(
|
||||
"cursor-pointer rounded-sm px-3 py-2 hover:bg-slate-700",
|
||||
{
|
||||
"bg-slate-700": isActive,
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
Recording
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="parameters"
|
||||
replace
|
||||
className={({ isActive }) => {
|
||||
return cn(
|
||||
"cursor-pointer rounded-sm px-3 py-2 hover:bg-slate-700",
|
||||
{
|
||||
"bg-slate-700": isActive,
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
Parameters
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="diagnostics"
|
||||
replace
|
||||
className={({ isActive }) => {
|
||||
return cn(
|
||||
"cursor-pointer rounded-sm px-3 py-2 hover:bg-slate-700",
|
||||
{
|
||||
"bg-slate-700": isActive,
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
Diagnostics
|
||||
</NavLink>
|
||||
</div>
|
||||
<SwitchBarNavigation
|
||||
options={[
|
||||
{
|
||||
label: "Actions",
|
||||
to: "actions",
|
||||
},
|
||||
{
|
||||
label: "Recording",
|
||||
to: "recording",
|
||||
},
|
||||
{
|
||||
label: "Parameters",
|
||||
to: "parameters",
|
||||
},
|
||||
{
|
||||
label: "Diagnostics",
|
||||
to: "diagnostics",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -35,6 +35,14 @@ export function statusIsFinalized({ status }: { status: Status }): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
export function statusIsAFailureType({ status }: { status: Status }): boolean {
|
||||
return (
|
||||
status === Status.Failed ||
|
||||
status === Status.Terminated ||
|
||||
status === Status.TimedOut
|
||||
);
|
||||
}
|
||||
|
||||
export function statusIsRunningOrQueued({
|
||||
status,
|
||||
}: {
|
||||
|
||||
Reference in New Issue
Block a user