Pre-convo UI (#3376)
This commit is contained in:
@@ -11,9 +11,11 @@ import { cn } from "@/util/utils";
|
|||||||
function OrgWalled({
|
function OrgWalled({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
|
hideTooltipContent,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
hideTooltipContent?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const isSkyvernUser = useIsSkyvernUser();
|
const isSkyvernUser = useIsSkyvernUser();
|
||||||
|
|
||||||
@@ -35,9 +37,13 @@ function OrgWalled({
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
{!hideTooltipContent && (
|
||||||
<p>This feature is only available to Skyvern organization members</p>
|
<TooltipContent>
|
||||||
</TooltipContent>
|
<p>
|
||||||
|
This feature is only available to Skyvern organization members
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
25
skyvern-frontend/src/components/icons/BugIcon.tsx
Normal file
25
skyvern-frontend/src/components/icons/BugIcon.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function BugIcon({ className }: Props) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
clipRule="evenodd"
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M7.293 3.293a1 1 0 0 1 1.414 0l1.876 1.876A6.39 6.39 0 0 1 12 5c.515 0 .996.049 1.445.14l1.848-1.847a1 1 0 1 1 1.414 1.414L15.45 5.965A5.5 5.5 0 0 1 17.249 8H18c.173 0 .456-.06.666-.212.159-.114.334-.314.334-.788a1 1 0 1 1 2 0c0 1.126-.491 1.926-1.166 2.412A3.233 3.233 0 0 1 18 10h-.086c.06.36.086.7.086 1v1h2a1 1 0 1 1 0 2h-2v1c0 .3-.026.64-.086 1H18c.493 0 1.211.14 1.834.588C20.51 17.075 21 17.875 21 19a1 1 0 1 1-2 0c0-.474-.175-.674-.334-.788A1.239 1.239 0 0 0 18 18h-.751a5.537 5.537 0 0 1-1.552 1.857C14.766 20.563 13.543 21 12 21c-1.543 0-2.765-.437-3.697-1.143-.7-.53-1.2-1.188-1.552-1.857H6c-.173 0-.456.06-.666.212-.159.114-.334.314-.334.788a1 1 0 1 1-2 0c0-1.126.492-1.926 1.166-2.412A3.233 3.233 0 0 1 6 16h.086c-.06-.36-.086-.7-.086-1v-1H4a1 1 0 1 1 0-2h2v-1c0-.349.022-.682.065-1H6c-.493 0-1.211-.14-1.834-.588C3.492 8.926 3 8.126 3 7a1 1 0 0 1 2 0c0 .474.175.674.334.788.21.152.493.212.666.212h.696A5.34 5.34 0 0 1 8.58 5.994L7.293 4.707a1 1 0 0 1 0-1.414zM12 9a1 1 0 1 0 0 2h.001a1 1 0 1 0 0-2H12zm-3 4a1 1 0 0 1 1-1h.001a1 1 0 1 1 0 2H10a1 1 0 0 1-1-1zm5-1a1 1 0 1 0 0 2h.001a1 1 0 1 0 0-2H14z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { BugIcon };
|
||||||
29
skyvern-frontend/src/components/icons/DebugIcon.tsx
Normal file
29
skyvern-frontend/src/components/icons/DebugIcon.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { PaperPlaneIcon } from "@radix-ui/react-icons";
|
||||||
|
|
||||||
|
import { cn } from "@/util/utils";
|
||||||
|
import { BugIcon } from "./BugIcon";
|
||||||
|
import { Tip } from "../Tip";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
// --
|
||||||
|
onClick?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function DebugIcon({ className, onClick }: Props) {
|
||||||
|
return (
|
||||||
|
<Tip content="Debug (pre-convo-UI)">
|
||||||
|
<div
|
||||||
|
className={cn("relative flex items-center justify-center", className)}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<PaperPlaneIcon className="size-6 cursor-pointer" />
|
||||||
|
<div className="absolute right-[-0.75rem] top-[-0.75rem] origin-center rotate-45 text-[#ff7e7e]">
|
||||||
|
<BugIcon className="scale-75" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { DebugIcon };
|
||||||
@@ -1,10 +1,5 @@
|
|||||||
import { getClient } from "@/api/AxiosClient";
|
import { getClient } from "@/api/AxiosClient";
|
||||||
import {
|
import { Createv2TaskRequest, ProxyLocation } from "@/api/types";
|
||||||
Createv2TaskRequest,
|
|
||||||
TaskV2,
|
|
||||||
ProxyLocation,
|
|
||||||
TaskGenerationApiResponse,
|
|
||||||
} from "@/api/types";
|
|
||||||
import img from "@/assets/promptBoxBg.png";
|
import img from "@/assets/promptBoxBg.png";
|
||||||
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
|
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
|
||||||
import { CartIcon } from "@/components/icons/CartIcon";
|
import { CartIcon } from "@/components/icons/CartIcon";
|
||||||
@@ -26,6 +21,7 @@ import {
|
|||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { toast } from "@/components/ui/use-toast";
|
import { toast } from "@/components/ui/use-toast";
|
||||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
|
import { WorkflowApiResponse } from "@/routes/workflows/types/workflowTypes";
|
||||||
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
||||||
import {
|
import {
|
||||||
FileTextIcon,
|
FileTextIcon,
|
||||||
@@ -38,7 +34,6 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { stringify as convertToYAML } from "yaml";
|
|
||||||
import {
|
import {
|
||||||
generatePhoneNumber,
|
generatePhoneNumber,
|
||||||
generateUniqueEmail,
|
generateUniqueEmail,
|
||||||
@@ -48,38 +43,7 @@ import {
|
|||||||
MAX_SCREENSHOT_SCROLLS_DEFAULT,
|
MAX_SCREENSHOT_SCROLLS_DEFAULT,
|
||||||
MAX_STEPS_DEFAULT,
|
MAX_STEPS_DEFAULT,
|
||||||
} from "@/routes/workflows/editor/nodes/Taskv2Node/types";
|
} from "@/routes/workflows/editor/nodes/Taskv2Node/types";
|
||||||
|
import { useAutoplayStore } from "@/store/useAutoplayStore";
|
||||||
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 exampleCases = [
|
const exampleCases = [
|
||||||
{
|
{
|
||||||
@@ -166,21 +130,22 @@ function PromptBox() {
|
|||||||
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
||||||
const [dataSchema, setDataSchema] = useState<string | null>(null);
|
const [dataSchema, setDataSchema] = useState<string | null>(null);
|
||||||
const [extraHttpHeaders, setExtraHttpHeaders] = useState<string | null>(null);
|
const [extraHttpHeaders, setExtraHttpHeaders] = useState<string | null>(null);
|
||||||
|
const { setAutoplay } = useAutoplayStore();
|
||||||
|
|
||||||
const startObserverCruiseMutation = useMutation({
|
const generateWorkflowMutation = useMutation({
|
||||||
mutationFn: async (prompt: string) => {
|
mutationFn: async (prompt: string) => {
|
||||||
const client = await getClient(credentialGetter, "v2");
|
const client = await getClient(credentialGetter, "sans-api-v1");
|
||||||
return client.post<Createv2TaskRequest, { data: TaskV2 }>(
|
|
||||||
"/tasks",
|
const result = await client.post<
|
||||||
|
Createv2TaskRequest,
|
||||||
|
{ data: WorkflowApiResponse }
|
||||||
|
>(
|
||||||
|
"/workflows/create-from-prompt",
|
||||||
{
|
{
|
||||||
user_prompt: prompt,
|
user_prompt: prompt,
|
||||||
webhook_callback_url: webhookCallbackUrl,
|
webhook_callback_url: webhookCallbackUrl,
|
||||||
proxy_location: proxyLocation,
|
proxy_location: proxyLocation,
|
||||||
browser_session_id: browserSessionId,
|
|
||||||
browser_address: cdpAddress,
|
|
||||||
totp_identifier: totpIdentifier,
|
totp_identifier: totpIdentifier,
|
||||||
generate_script: generateScript,
|
|
||||||
publish_workflow: publishWorkflow || generateScript,
|
|
||||||
max_screenshot_scrolls: maxScreenshotScrolls,
|
max_screenshot_scrolls: maxScreenshotScrolls,
|
||||||
extracted_information_schema: dataSchema
|
extracted_information_schema: dataSchema
|
||||||
? (() => {
|
? (() => {
|
||||||
@@ -207,76 +172,32 @@ function PromptBox() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
},
|
},
|
||||||
onSuccess: (response) => {
|
onSuccess: ({ data: workflow }) => {
|
||||||
toast({
|
toast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
title: "Workflow Run Created",
|
title: "Workflow Created",
|
||||||
description: `Workflow run created successfully.`,
|
description: `Workflow created successfully.`,
|
||||||
});
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ["workflowRuns"],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ["workflows"],
|
queryKey: ["workflows"],
|
||||||
});
|
});
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ["runs"],
|
|
||||||
});
|
|
||||||
navigate(
|
|
||||||
`/workflows/${response.data.workflow_permanent_id}/${response.data.workflow_run_id}`,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onError: (error: AxiosError) => {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Error creating workflow run from prompt",
|
|
||||||
description: error.message,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const getTaskFromPromptMutation = useMutation({
|
const firstBlock = workflow.workflow_definition.blocks[0];
|
||||||
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) => {
|
|
||||||
const detail = (error.response?.data as { detail?: string })?.detail;
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Error creating task from prompt",
|
|
||||||
description: detail ? detail : error.message,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const saveTaskMutation = useMutation({
|
if (firstBlock) {
|
||||||
mutationFn: async (params: TaskGenerationApiResponse) => {
|
setAutoplay(workflow.workflow_permanent_id, firstBlock.label);
|
||||||
const client = await getClient(credentialGetter);
|
}
|
||||||
const templateTask =
|
|
||||||
createTemplateTaskFromTaskGenerationParameters(params);
|
navigate(`/workflows/${workflow.workflow_permanent_id}/debug`);
|
||||||
const yaml = convertToYAML(templateTask);
|
|
||||||
return client.post("/workflows", yaml, {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "text/plain",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ["savedTasks"],
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
onError: (error: AxiosError) => {
|
onError: (error: AxiosError) => {
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: "Error saving task",
|
title: "Error creating workflow from prompt",
|
||||||
description: error.message,
|
description: error.message,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -343,30 +264,21 @@ function PromptBox() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{startObserverCruiseMutation.isPending ||
|
{generateWorkflowMutation.isPending ? (
|
||||||
getTaskFromPromptMutation.isPending ||
|
|
||||||
saveTaskMutation.isPending ? (
|
|
||||||
<ReloadIcon className="size-6 animate-spin" />
|
<ReloadIcon className="size-6 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<PaperPlaneIcon
|
<div className="flex items-center">
|
||||||
className="size-6 cursor-pointer"
|
{generateWorkflowMutation.isPending ? (
|
||||||
onClick={async () => {
|
<ReloadIcon className="size-6 animate-spin" />
|
||||||
if (selectValue === "v2") {
|
) : (
|
||||||
startObserverCruiseMutation.mutate(prompt);
|
<PaperPlaneIcon
|
||||||
return;
|
className="size-6 cursor-pointer"
|
||||||
}
|
onClick={async () => {
|
||||||
const taskGenerationResponse =
|
generateWorkflowMutation.mutate(prompt);
|
||||||
await getTaskFromPromptMutation.mutateAsync(prompt);
|
}}
|
||||||
await saveTaskMutation.mutateAsync(
|
/>
|
||||||
taskGenerationResponse,
|
)}
|
||||||
);
|
</div>
|
||||||
navigate("/tasks/create/from-prompt", {
|
|
||||||
state: {
|
|
||||||
data: taskGenerationResponse,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ function Workspace({
|
|||||||
const { blockLabel, workflowPermanentId } = useParams();
|
const { blockLabel, workflowPermanentId } = useParams();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const cacheKeyValueParam = searchParams.get("cache-key-value");
|
const cacheKeyValueParam = searchParams.get("cache-key-value");
|
||||||
const [timelineMode, setTimelineMode] = useState("narrow");
|
const [timelineMode, setTimelineMode] = useState("wide");
|
||||||
const [cacheKeyValueFilter, setCacheKeyValueFilter] = useState<string | null>(
|
const [cacheKeyValueFilter, setCacheKeyValueFilter] = useState<string | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { MAX_STEPS_DEFAULT, type Taskv2Node } from "./types";
|
|||||||
import { ModelSelector } from "@/components/ModelSelector";
|
import { ModelSelector } from "@/components/ModelSelector";
|
||||||
import { cn } from "@/util/utils";
|
import { cn } from "@/util/utils";
|
||||||
import { NodeHeader } from "../components/NodeHeader";
|
import { NodeHeader } from "../components/NodeHeader";
|
||||||
|
import { NodeFooter } from "../components/NodeFooter";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||||
@@ -195,6 +196,7 @@ function Taskv2Node({ id, data, type }: NodeProps<Taskv2Node>) {
|
|||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
<NodeFooter blockLabel={label} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { toast } from "@/components/ui/use-toast";
|
|||||||
import { useLogging } from "@/hooks/useLogging";
|
import { useLogging } from "@/hooks/useLogging";
|
||||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
import { useOnChange } from "@/hooks/useOnChange";
|
import { useOnChange } from "@/hooks/useOnChange";
|
||||||
|
import { useAutoplayStore } from "@/store/useAutoplayStore";
|
||||||
|
|
||||||
import { useNodeLabelChangeHandler } from "@/routes/workflows/hooks/useLabelChangeHandler";
|
import { useNodeLabelChangeHandler } from "@/routes/workflows/hooks/useLabelChangeHandler";
|
||||||
import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback";
|
import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback";
|
||||||
@@ -186,6 +187,28 @@ function NodeHeader({
|
|||||||
const [workflowRunStatus, setWorkflowRunStatus] = useState(
|
const [workflowRunStatus, setWorkflowRunStatus] = useState(
|
||||||
workflowRun?.status,
|
workflowRun?.status,
|
||||||
);
|
);
|
||||||
|
const { getAutoplay, setAutoplay } = useAutoplayStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!debugSession) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const details = getAutoplay();
|
||||||
|
|
||||||
|
if (
|
||||||
|
workflowPermanentId === details.wpid &&
|
||||||
|
blockLabel === details.blockLabel
|
||||||
|
) {
|
||||||
|
setAutoplay(null, null);
|
||||||
|
setTimeout(() => {
|
||||||
|
runBlock.mutateAsync();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// on mount
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [debugSession]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setWorkflowRunStatus(workflowRun?.status);
|
setWorkflowRunStatus(workflowRun?.status);
|
||||||
@@ -496,8 +519,10 @@ function NodeHeader({
|
|||||||
) : (
|
) : (
|
||||||
<PlayIcon
|
<PlayIcon
|
||||||
className={cn("size-6", {
|
className={cn("size-6", {
|
||||||
"fill-gray-500 text-gray-500":
|
"pointer-events-none fill-gray-500 text-gray-500":
|
||||||
workflowRunIsRunningOrQueued || !workflowPermanentId,
|
workflowRunIsRunningOrQueued ||
|
||||||
|
!workflowPermanentId ||
|
||||||
|
debugSession === undefined,
|
||||||
})}
|
})}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleOnPlay();
|
handleOnPlay();
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ function useCacheKeyValuesQuery({
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
enabled: !!workflowPermanentId,
|
enabled: !!workflowPermanentId && !!cacheKey && cacheKey.length > 0,
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
staleTime: 5 * 60 * 1000,
|
staleTime: 5 * 60 * 1000,
|
||||||
});
|
});
|
||||||
|
|||||||
24
skyvern-frontend/src/store/useAutoplayStore.ts
Normal file
24
skyvern-frontend/src/store/useAutoplayStore.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
type AutoplayStore = {
|
||||||
|
wpid: string | null;
|
||||||
|
blockLabel: string | null;
|
||||||
|
setAutoplay: (wpid: string | null, blockLabel: string | null) => void;
|
||||||
|
clearAutoplay: () => void;
|
||||||
|
getAutoplay: () => { wpid: string | null; blockLabel: string | null };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAutoplayStore = create<AutoplayStore>((set, get) => ({
|
||||||
|
wpid: null,
|
||||||
|
blockLabel: null,
|
||||||
|
setAutoplay: (wpid: string | null, blockLabel: string | null) => {
|
||||||
|
set({ wpid, blockLabel });
|
||||||
|
},
|
||||||
|
clearAutoplay: () => {
|
||||||
|
set({ wpid: null, blockLabel: null });
|
||||||
|
},
|
||||||
|
getAutoplay: () => {
|
||||||
|
const { wpid, blockLabel } = get();
|
||||||
|
return { wpid, blockLabel };
|
||||||
|
},
|
||||||
|
}));
|
||||||
Reference in New Issue
Block a user