diff --git a/skyvern-frontend/src/assets/promptBoxBg.png b/skyvern-frontend/src/assets/promptBoxBg.png
new file mode 100644
index 00000000..82a4e8b0
Binary files /dev/null and b/skyvern-frontend/src/assets/promptBoxBg.png differ
diff --git a/skyvern-frontend/src/components/SwitchBar.tsx b/skyvern-frontend/src/components/SwitchBar.tsx
new file mode 100644
index 00000000..2a0e3fda
--- /dev/null
+++ b/skyvern-frontend/src/components/SwitchBar.tsx
@@ -0,0 +1,42 @@
+import { cn } from "@/util/utils";
+
+type Option = {
+ label: string;
+ value: string;
+};
+
+type Props = {
+ options: Option[];
+ value: string;
+ onChange: (value: string) => void;
+};
+
+function SwitchBar({ options, value, onChange }: Props) {
+ return (
+
+ {options.map((option) => {
+ const selected = option.value === value;
+ return (
+
{
+ if (!selected) {
+ onChange(option.value);
+ }
+ }}
+ >
+ {option.label}
+
+ );
+ })}
+
+ );
+}
+
+export { SwitchBar };
diff --git a/skyvern-frontend/src/routes/tasks/create/PromptBox.tsx b/skyvern-frontend/src/routes/tasks/create/PromptBox.tsx
new file mode 100644
index 00000000..56705ed3
--- /dev/null
+++ b/skyvern-frontend/src/routes/tasks/create/PromptBox.tsx
@@ -0,0 +1,200 @@
+import { getClient } from "@/api/AxiosClient";
+import { TaskGenerationApiResponse } from "@/api/types";
+import img from "@/assets/promptBoxBg.png";
+import { Textarea } from "@/components/ui/textarea";
+import { toast } from "@/components/ui/use-toast";
+import { useCredentialGetter } from "@/hooks/useCredentialGetter";
+import { PaperPlaneIcon, ReloadIcon } from "@radix-ui/react-icons";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { AxiosError } from "axios";
+import { useState } from "react";
+import { useNavigate } from "react-router-dom";
+import { stringify as convertToYAML } from "yaml";
+
+function createTaskFromTaskGenerationParameters(
+ values: TaskGenerationApiResponse,
+) {
+ return {
+ url: values.url,
+ navigation_goal: values.navigation_goal,
+ data_extraction_goal: values.data_extraction_goal,
+ proxy_location: "RESIDENTIAL",
+ navigation_payload: values.navigation_payload,
+ extracted_information_schema: values.extracted_information_schema,
+ };
+}
+
+function createTemplateTaskFromTaskGenerationParameters(
+ values: TaskGenerationApiResponse,
+) {
+ return {
+ title: values.suggested_title ?? "Untitled Task",
+ description: "",
+ is_saved_task: true,
+ webhook_callback_url: null,
+ proxy_location: "RESIDENTIAL",
+ workflow_definition: {
+ parameters: [
+ {
+ parameter_type: "workflow",
+ workflow_parameter_type: "json",
+ key: "navigation_payload",
+ default_value: JSON.stringify(values.navigation_payload),
+ },
+ ],
+ blocks: [
+ {
+ block_type: "task",
+ label: values.suggested_title ?? "Untitled Task",
+ url: values.url,
+ navigation_goal: values.navigation_goal,
+ data_extraction_goal: values.data_extraction_goal,
+ data_schema: values.extracted_information_schema,
+ },
+ ],
+ },
+ };
+}
+
+const examplePrompts = [
+ "What is the top post on hackernews?",
+ "Navigate to Google Finance and search for AAPL",
+];
+
+function PromptBox() {
+ const navigate = useNavigate();
+ const [prompt, setPrompt] = useState("");
+ const credentialGetter = useCredentialGetter();
+ const queryClient = useQueryClient();
+
+ const getTaskFromPromptMutation = useMutation({
+ mutationFn: async (prompt: string) => {
+ const client = await getClient(credentialGetter);
+ return client
+ .post<
+ { prompt: string },
+ { data: TaskGenerationApiResponse }
+ >("/generate/task", { prompt })
+ .then((response) => response.data);
+ },
+ onError: (error: AxiosError) => {
+ toast({
+ variant: "destructive",
+ title: "Error creating task from prompt",
+ description: error.message,
+ });
+ },
+ });
+
+ const saveTaskMutation = useMutation({
+ mutationFn: async (params: TaskGenerationApiResponse) => {
+ const client = await getClient(credentialGetter);
+ const templateTask =
+ createTemplateTaskFromTaskGenerationParameters(params);
+ const yaml = convertToYAML(templateTask);
+ return client.post("/workflows", yaml, {
+ headers: {
+ "Content-Type": "text/plain",
+ },
+ });
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: ["savedTasks"],
+ });
+ },
+ onError: (error: AxiosError) => {
+ toast({
+ variant: "destructive",
+ title: "Error saving task",
+ description: error.message,
+ });
+ },
+ });
+
+ const runTaskMutation = useMutation({
+ mutationFn: async (params: TaskGenerationApiResponse) => {
+ const client = await getClient(credentialGetter);
+ const data = createTaskFromTaskGenerationParameters(params);
+ return client.post<
+ ReturnType,
+ { data: { task_id: string } }
+ >("/tasks", data);
+ },
+ onSuccess: (response) => {
+ navigate(`/tasks/${response.data.task_id}/actions`);
+ },
+ onError: (error: AxiosError) => {
+ toast({
+ variant: "destructive",
+ title: "Error running task",
+ description: error.message,
+ });
+ },
+ });
+
+ return (
+
+
+
+
+ What task would you like to accomplish?
+
+
+
+
+
+ {examplePrompts.map((examplePrompt) => {
+ return (
+
{
+ setPrompt(examplePrompt);
+ }}
+ >
+ {examplePrompt}
+
+ );
+ })}
+
+
+ );
+}
+
+export { PromptBox };
diff --git a/skyvern-frontend/src/routes/tasks/create/SavedTaskCard.tsx b/skyvern-frontend/src/routes/tasks/create/SavedTaskCard.tsx
index 59a5de65..0679a7be 100644
--- a/skyvern-frontend/src/routes/tasks/create/SavedTaskCard.tsx
+++ b/skyvern-frontend/src/routes/tasks/create/SavedTaskCard.tsx
@@ -14,7 +14,6 @@ import {
DialogFooter,
DialogHeader,
DialogTitle,
- DialogTrigger,
} from "@/components/ui/dialog";
import {
DropdownMenu,
@@ -26,6 +25,7 @@ import {
} from "@/components/ui/dropdown-menu";
import { toast } from "@/components/ui/use-toast";
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
+import { cn } from "@/util/utils";
import { DotsHorizontalIcon, ReloadIcon } from "@radix-ui/react-icons";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
@@ -43,6 +43,7 @@ function SavedTaskCard({ workflowId, title, url, description }: Props) {
const credentialGetter = useCredentialGetter();
const queryClient = useQueryClient();
const navigate = useNavigate();
+ const [hovering, setHovering] = useState(false);
const deleteTaskMutation = useMutation({
mutationFn: async (id: string) => {
@@ -73,29 +74,42 @@ function SavedTaskCard({ workflowId, title, url, description }: Props) {
});
return (
-
-
-
+ setHovering(true)}
+ onMouseLeave={() => setHovering(false)}
+ onMouseOver={() => setHovering(true)}
+ onMouseOut={() => setHovering(false)}
+ >
+
+
{title}
-
-
+
{url}
{
navigate(workflowId);
}}
diff --git a/skyvern-frontend/src/routes/tasks/create/SavedTasks.tsx b/skyvern-frontend/src/routes/tasks/create/SavedTasks.tsx
index c7531a92..2b9011ec 100644
--- a/skyvern-frontend/src/routes/tasks/create/SavedTasks.tsx
+++ b/skyvern-frontend/src/routes/tasks/create/SavedTasks.tsx
@@ -15,6 +15,8 @@ import { useMutation, useQuery } from "@tanstack/react-query";
import { useNavigate } from "react-router-dom";
import { stringify as convertToYAML } from "yaml";
import { SavedTaskCard } from "./SavedTaskCard";
+import { useState } from "react";
+import { cn } from "@/util/utils";
function createEmptyTaskTemplate() {
return {
@@ -49,6 +51,7 @@ function createEmptyTaskTemplate() {
function SavedTasks() {
const credentialGetter = useCredentialGetter();
const navigate = useNavigate();
+ const [hovering, setHovering] = useState(false);
const { data } = useQuery>({
queryKey: ["savedTasks"],
@@ -99,20 +102,36 @@ function SavedTasks() {
return (
{
- if (mutation.isPending) {
- return;
- }
- mutation.mutate();
- }}
+ className="border-0"
+ onMouseEnter={() => setHovering(true)}
+ onMouseLeave={() => setHovering(false)}
+ onMouseOver={() => setHovering(true)}
+ onMouseOut={() => setHovering(false)}
>
-
- New Template
-
+
+ New Template
+
Create your own template
-
+ {
+ if (mutation.isPending) {
+ return;
+ }
+ mutation.mutate();
+ }}
+ >
{!mutation.isPending && }
{mutation.isPending && (
diff --git a/skyvern-frontend/src/routes/tasks/create/TaskTemplateCard.tsx b/skyvern-frontend/src/routes/tasks/create/TaskTemplateCard.tsx
new file mode 100644
index 00000000..0a735df5
--- /dev/null
+++ b/skyvern-frontend/src/routes/tasks/create/TaskTemplateCard.tsx
@@ -0,0 +1,56 @@
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { cn } from "@/util/utils";
+import { useState } from "react";
+
+type Props = {
+ title: string;
+ description: string;
+ body: string;
+ onClick: () => void;
+};
+
+function TaskTemplateCard({ title, description, body, onClick }: Props) {
+ const [hovering, setHovering] = useState(false);
+
+ return (
+ setHovering(true)}
+ onMouseLeave={() => setHovering(false)}
+ onMouseOver={() => setHovering(true)}
+ onMouseOut={() => setHovering(false)}
+ >
+
+ {title}
+
+ {description}
+
+
+ {
+ onClick();
+ }}
+ >
+ {body}
+
+
+ );
+}
+
+export { TaskTemplateCard };
diff --git a/skyvern-frontend/src/routes/tasks/create/TaskTemplates.tsx b/skyvern-frontend/src/routes/tasks/create/TaskTemplates.tsx
index 5f6db066..eb43a5c1 100644
--- a/skyvern-frontend/src/routes/tasks/create/TaskTemplates.tsx
+++ b/skyvern-frontend/src/routes/tasks/create/TaskTemplates.tsx
@@ -1,35 +1,11 @@
-import { SampleCase } from "../types";
-import {
- Card,
- CardContent,
- CardDescription,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import { Separator } from "@/components/ui/separator";
import { useNavigate } from "react-router-dom";
-import { SavedTasks } from "./SavedTasks";
import { getSample } from "../data/sampleTaskData";
-import { Textarea } from "@/components/ui/textarea";
+import { SampleCase } from "../types";
+import { PromptBox } from "./PromptBox";
+import { SavedTasks } from "./SavedTasks";
+import { SwitchBar } from "@/components/SwitchBar";
import { useState } from "react";
-import {
- InfoCircledIcon,
- PaperPlaneIcon,
- ReloadIcon,
-} from "@radix-ui/react-icons";
-import { useMutation, useQueryClient } from "@tanstack/react-query";
-import { useCredentialGetter } from "@/hooks/useCredentialGetter";
-import { getClient } from "@/api/AxiosClient";
-import { AxiosError } from "axios";
-import { toast } from "@/components/ui/use-toast";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { TaskGenerationApiResponse } from "@/api/types";
-import { stringify as convertToYAML } from "yaml";
-
-const examplePrompts = [
- "What is the top post on hackernews?",
- "Navigate to Google Finance and search for AAPL",
-];
+import { TaskTemplateCard } from "./TaskTemplateCard";
const templateSamples: {
[key in SampleCase]: {
@@ -63,228 +39,52 @@ const templateSamples: {
},
};
-function createTemplateTaskFromTaskGenerationParameters(
- values: TaskGenerationApiResponse,
-) {
- return {
- title: values.suggested_title ?? "Untitled Task",
- description: "",
- is_saved_task: true,
- webhook_callback_url: null,
- proxy_location: "RESIDENTIAL",
- workflow_definition: {
- parameters: [
- {
- parameter_type: "workflow",
- workflow_parameter_type: "json",
- key: "navigation_payload",
- default_value: JSON.stringify(values.navigation_payload),
- },
- ],
- blocks: [
- {
- block_type: "task",
- label: values.suggested_title ?? "Untitled Task",
- url: values.url,
- navigation_goal: values.navigation_goal,
- data_extraction_goal: values.data_extraction_goal,
- data_schema: values.extracted_information_schema,
- },
- ],
- },
- };
-}
-
-function createTaskFromTaskGenerationParameters(
- values: TaskGenerationApiResponse,
-) {
- return {
- url: values.url,
- navigation_goal: values.navigation_goal,
- data_extraction_goal: values.data_extraction_goal,
- proxy_location: "RESIDENTIAL",
- navigation_payload: values.navigation_payload,
- extracted_information_schema: values.extracted_information_schema,
- };
-}
+const templateSwitchOptions = [
+ {
+ label: "Skyvern Templates",
+ value: "skyvern",
+ },
+ {
+ label: "My Templates",
+ value: "user",
+ },
+];
function TaskTemplates() {
const navigate = useNavigate();
- const [prompt, setPrompt] = useState("");
- const credentialGetter = useCredentialGetter();
- const queryClient = useQueryClient();
-
- const getTaskFromPromptMutation = useMutation({
- mutationFn: async (prompt: string) => {
- const client = await getClient(credentialGetter);
- return client
- .post<
- { prompt: string },
- { data: TaskGenerationApiResponse }
- >("/generate/task", { prompt })
- .then((response) => response.data);
- },
- onError: (error: AxiosError) => {
- toast({
- variant: "destructive",
- title: "Error creating task from prompt",
- description: error.message,
- });
- },
- });
-
- const saveTaskMutation = useMutation({
- mutationFn: async (params: TaskGenerationApiResponse) => {
- const client = await getClient(credentialGetter);
- const templateTask =
- createTemplateTaskFromTaskGenerationParameters(params);
- const yaml = convertToYAML(templateTask);
- return client.post("/workflows", yaml, {
- headers: {
- "Content-Type": "text/plain",
- },
- });
- },
- onSuccess: () => {
- queryClient.invalidateQueries({
- queryKey: ["savedTasks"],
- });
- },
- onError: (error: AxiosError) => {
- toast({
- variant: "destructive",
- title: "Error saving task",
- description: error.message,
- });
- },
- });
-
- const runTaskMutation = useMutation({
- mutationFn: async (params: TaskGenerationApiResponse) => {
- const client = await getClient(credentialGetter);
- const data = createTaskFromTaskGenerationParameters(params);
- return client.post<
- ReturnType,
- { data: { task_id: string } }
- >("/tasks", data);
- },
- onSuccess: (response) => {
- navigate(`/tasks/${response.data.task_id}/actions`);
- },
- onError: (error: AxiosError) => {
- toast({
- variant: "destructive",
- title: "Error running task",
- description: error.message,
- });
- },
- });
+ const [templateSwitchValue, setTemplateSwitchValue] =
+ useState<(typeof templateSwitchOptions)[number]["value"]>("skyvern");
return (
-
-
-
-
- Have a complicated workflow you would like to automate?
-
-
-
- Book a demo {"->"}
-
-
-
-
-
-
- We will generate a task for you automatically.
-
-
-
-
- {examplePrompts.map((examplePrompt) => {
- return (
-
{
- setPrompt(examplePrompt);
- }}
- >
- {examplePrompt}
-
- );
- })}
-
+
+
+
-
-
- Your saved task templates
-
-
-
-
-
-
- Sample tasks that showcase Skyvern's capabilities
-
-
-
- {Object.entries(templateSamples).map(([sampleKey, sample]) => {
- return (
-
-
- {sample.title}
-
- {getSample(sampleKey as SampleCase).url}
-
-
-
+ {templateSwitchValue === "skyvern" ? (
+
+ {Object.entries(templateSamples).map(([sampleKey, sample]) => {
+ return (
+ {
- navigate(sampleKey);
+ navigate(`/create/${sampleKey}`);
}}
- >
- {sample.description}
-
-
- );
- })}
-
+ />
+ );
+ })}
+
+ ) : (
+
+ )}
);