Build a banner for the workflows page (#1591)
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { getClient } from "@/api/AxiosClient";
|
import { getClient } from "@/api/AxiosClient";
|
||||||
|
import { WorkflowRunApiResponse } from "@/api/types";
|
||||||
import { StatusBadge } from "@/components/StatusBadge";
|
import { StatusBadge } from "@/components/StatusBadge";
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Pagination,
|
Pagination,
|
||||||
@@ -25,40 +25,20 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
|
import { downloadBlob } from "@/util/downloadBlob";
|
||||||
import { basicLocalTimeFormat, basicTimeFormat } from "@/util/timeFormat";
|
import { basicLocalTimeFormat, basicTimeFormat } from "@/util/timeFormat";
|
||||||
import { cn } from "@/util/utils";
|
import { cn } from "@/util/utils";
|
||||||
import {
|
import { DownloadIcon, Pencil2Icon, PlayIcon } from "@radix-ui/react-icons";
|
||||||
DownloadIcon,
|
import { useQuery } from "@tanstack/react-query";
|
||||||
ExclamationTriangleIcon,
|
|
||||||
Pencil2Icon,
|
|
||||||
PlayIcon,
|
|
||||||
PlusIcon,
|
|
||||||
ReloadIcon,
|
|
||||||
} from "@radix-ui/react-icons";
|
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
||||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
import { stringify as convertToYAML } from "yaml";
|
|
||||||
import { ImportWorkflowButton } from "./ImportWorkflowButton";
|
|
||||||
import { WorkflowCreateYAMLRequest } from "./types/workflowYamlTypes";
|
|
||||||
import { WorkflowActions } from "./WorkflowActions";
|
|
||||||
import { WorkflowTitle } from "./WorkflowTitle";
|
|
||||||
import { WorkflowApiResponse } from "./types/workflowTypes";
|
import { WorkflowApiResponse } from "./types/workflowTypes";
|
||||||
import { WorkflowRunApiResponse } from "@/api/types";
|
import { WorkflowActions } from "./WorkflowActions";
|
||||||
import { downloadBlob } from "@/util/downloadBlob";
|
import { WorkflowsPageBanner } from "./WorkflowsPageBanner";
|
||||||
|
import { WorkflowTitle } from "./WorkflowTitle";
|
||||||
const emptyWorkflowRequest: WorkflowCreateYAMLRequest = {
|
|
||||||
title: "New Workflow",
|
|
||||||
description: "",
|
|
||||||
workflow_definition: {
|
|
||||||
blocks: [],
|
|
||||||
parameters: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function Workflows() {
|
function Workflows() {
|
||||||
const credentialGetter = useCredentialGetter();
|
const credentialGetter = useCredentialGetter();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const workflowsPage = searchParams.get("workflowsPage")
|
const workflowsPage = searchParams.get("workflowsPage")
|
||||||
? Number(searchParams.get("workflowsPage"))
|
? Number(searchParams.get("workflowsPage"))
|
||||||
@@ -99,27 +79,6 @@ function Workflows() {
|
|||||||
refetchOnMount: "always",
|
refetchOnMount: "always",
|
||||||
});
|
});
|
||||||
|
|
||||||
const createNewWorkflowMutation = useMutation({
|
|
||||||
mutationFn: async () => {
|
|
||||||
const client = await getClient(credentialGetter);
|
|
||||||
const yaml = convertToYAML(emptyWorkflowRequest);
|
|
||||||
return client.post<
|
|
||||||
typeof emptyWorkflowRequest,
|
|
||||||
{ data: WorkflowApiResponse }
|
|
||||||
>("/workflows", yaml, {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "text/plain",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onSuccess: (response) => {
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ["workflows"],
|
|
||||||
});
|
|
||||||
navigate(`/workflows/${response.data.workflow_permanent_id}/edit`);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleExport() {
|
function handleExport() {
|
||||||
if (!workflowRuns) {
|
if (!workflowRuns) {
|
||||||
return; // should never happen
|
return; // should never happen
|
||||||
@@ -176,55 +135,12 @@ function Workflows() {
|
|||||||
navigate(path);
|
navigate(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
const showExperimentalMessage =
|
|
||||||
workflows?.length === 0 && workflowsPage === 1;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{showExperimentalMessage && (
|
<WorkflowsPageBanner />
|
||||||
<Alert variant="default" className="bg-slate-elevation2">
|
|
||||||
<AlertTitle>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<ExclamationTriangleIcon className="h-6 w-6" />
|
|
||||||
<span className="text-xl">Experimental Feature</span>
|
|
||||||
</div>
|
|
||||||
</AlertTitle>
|
|
||||||
<AlertDescription className="text-base text-slate-300">
|
|
||||||
Workflows are still in experimental mode. Please{" "}
|
|
||||||
{
|
|
||||||
<a
|
|
||||||
href="https://meetings.hubspot.com/skyvern/demo"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="ml-auto underline underline-offset-2"
|
|
||||||
>
|
|
||||||
book a demo
|
|
||||||
</a>
|
|
||||||
}{" "}
|
|
||||||
if you'd like to learn more. If you're feeling adventurous, create
|
|
||||||
your first workflow!
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<header className="flex items-center justify-between">
|
<header>
|
||||||
<h1 className="text-2xl font-semibold">Workflows</h1>
|
<h1 className="text-2xl font-semibold">Workflows</h1>
|
||||||
<div className="flex gap-2">
|
|
||||||
<ImportWorkflowButton />
|
|
||||||
<Button
|
|
||||||
disabled={createNewWorkflowMutation.isPending}
|
|
||||||
onClick={() => {
|
|
||||||
createNewWorkflowMutation.mutate();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{createNewWorkflowMutation.isPending ? (
|
|
||||||
<ReloadIcon className="mr-2 h-4 w-4 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<PlusIcon className="mr-2 h-4 w-4" />
|
|
||||||
)}
|
|
||||||
Create Workflow
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div className="rounded-md border">
|
<div className="rounded-md border">
|
||||||
<Table>
|
<Table>
|
||||||
|
|||||||
102
skyvern-frontend/src/routes/workflows/WorkflowsPageBanner.tsx
Normal file
102
skyvern-frontend/src/routes/workflows/WorkflowsPageBanner.tsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { ImportWorkflowButton } from "./ImportWorkflowButton";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { getClient } from "@/api/AxiosClient";
|
||||||
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
|
import { WorkflowCreateYAMLRequest } from "./types/workflowYamlTypes";
|
||||||
|
import { stringify as convertToYAML } from "yaml";
|
||||||
|
import { WorkflowApiResponse } from "./types/workflowTypes";
|
||||||
|
import { PlusIcon, ReloadIcon } from "@radix-ui/react-icons";
|
||||||
|
|
||||||
|
const emptyWorkflowRequest: WorkflowCreateYAMLRequest = {
|
||||||
|
title: "New Workflow",
|
||||||
|
description: "",
|
||||||
|
workflow_definition: {
|
||||||
|
blocks: [],
|
||||||
|
parameters: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function WorkflowsPageBanner() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const credentialGetter = useCredentialGetter();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const createNewWorkflowMutation = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
const client = await getClient(credentialGetter);
|
||||||
|
const yaml = convertToYAML(emptyWorkflowRequest);
|
||||||
|
return client.post<
|
||||||
|
typeof emptyWorkflowRequest,
|
||||||
|
{ data: WorkflowApiResponse }
|
||||||
|
>("/workflows", yaml, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "text/plain",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: (response) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["workflows"],
|
||||||
|
});
|
||||||
|
navigate(`/workflows/${response.data.workflow_permanent_id}/edit`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-8 bg-slate-elevation1 p-12">
|
||||||
|
<div className="flex justify-center text-3xl font-bold">
|
||||||
|
<h1>Workflows</h1>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-center gap-4">
|
||||||
|
<ImportWorkflowButton />
|
||||||
|
<Button
|
||||||
|
disabled={createNewWorkflowMutation.isPending}
|
||||||
|
onClick={() => {
|
||||||
|
createNewWorkflowMutation.mutate();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{createNewWorkflowMutation.isPending ? (
|
||||||
|
<ReloadIcon className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<PlusIcon className="mr-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
Create Workflow
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<div className="mx-auto flex flex-col gap-3">
|
||||||
|
<div className="font-bold">
|
||||||
|
Workflows let you create complex web-agents that can:
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex size-6 items-center justify-center rounded-full bg-primary text-primary-foreground">
|
||||||
|
1
|
||||||
|
</div>
|
||||||
|
<div>Save browser sessions and re-use them in subsequent runs</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex size-6 items-center justify-center rounded-full bg-primary text-primary-foreground">
|
||||||
|
2
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Connect multiple agents together to carry out complex objectives
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex size-6 items-center justify-center rounded-full bg-primary text-primary-foreground">
|
||||||
|
3
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Allow Skyvern agents to execute non-browser tasks such as sending
|
||||||
|
emails, or parsing PDFs
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { WorkflowsPageBanner };
|
||||||
Reference in New Issue
Block a user