add browser session id to workflow run form (#2686)
This commit is contained in:
20
skyvern-frontend/src/hooks/useLocalStorageFormDefault.ts
Normal file
20
skyvern-frontend/src/hooks/useLocalStorageFormDefault.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Returns a value from localStorage for the given key, or a fallback if not present.
|
||||||
|
* Use this hook to initialize form default values from localStorage in a type-safe way.
|
||||||
|
*
|
||||||
|
* @param storageKey - The localStorage key to read
|
||||||
|
* @param fallback - The fallback value if localStorage is empty or unavailable
|
||||||
|
* @returns The value from localStorage (if present), otherwise the fallback
|
||||||
|
*/
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
export function useLocalStorageFormDefault(
|
||||||
|
storageKey: string,
|
||||||
|
fallback: string | null | undefined,
|
||||||
|
): string | null | undefined {
|
||||||
|
return useMemo(() => {
|
||||||
|
if (typeof window === "undefined") return fallback ?? null;
|
||||||
|
const value = localStorage.getItem(storageKey);
|
||||||
|
return value !== null ? value : fallback ?? null;
|
||||||
|
}, [storageKey, fallback]);
|
||||||
|
}
|
||||||
23
skyvern-frontend/src/hooks/useSyncFormFieldToStorage.ts
Normal file
23
skyvern-frontend/src/hooks/useSyncFormFieldToStorage.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { UseFormReturn, FieldValues, WatchObserver } from "react-hook-form";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs a form field value to localStorage whenever it changes.
|
||||||
|
* @param form - A react-hook-form object with a .watch method
|
||||||
|
* @param fieldName - The name of the field to watch
|
||||||
|
* @param storageKey - The key to write to in localStorage
|
||||||
|
*/
|
||||||
|
export function useSyncFormFieldToStorage<T extends FieldValues>(
|
||||||
|
form: UseFormReturn<T>,
|
||||||
|
fieldName: keyof T & string,
|
||||||
|
storageKey: string,
|
||||||
|
) {
|
||||||
|
useEffect(() => {
|
||||||
|
const subscription = form.watch(((value, { name }) => {
|
||||||
|
if (name === fieldName && typeof value[fieldName] === "string") {
|
||||||
|
localStorage.setItem(storageKey, value[fieldName] as string);
|
||||||
|
}
|
||||||
|
}) as WatchObserver<T>);
|
||||||
|
return () => subscription.unsubscribe();
|
||||||
|
}, [form, fieldName, storageKey]);
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { toast } from "@/components/ui/use-toast";
|
import { toast } from "@/components/ui/use-toast";
|
||||||
import { useApiCredential } from "@/hooks/useApiCredential";
|
import { useApiCredential } from "@/hooks/useApiCredential";
|
||||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
|
import { useSyncFormFieldToStorage } from "@/hooks/useSyncFormFieldToStorage";
|
||||||
|
import { useLocalStorageFormDefault } from "@/hooks/useLocalStorageFormDefault";
|
||||||
import { apiBaseUrl } from "@/util/env";
|
import { apiBaseUrl } from "@/util/env";
|
||||||
import { type ApiCommandOptions } from "@/util/apiCommands";
|
import { type ApiCommandOptions } from "@/util/apiCommands";
|
||||||
import { PlayIcon, ReloadIcon } from "@radix-ui/react-icons";
|
import { PlayIcon, ReloadIcon } from "@radix-ui/react-icons";
|
||||||
@@ -70,22 +72,27 @@ type RunWorkflowRequestBody = {
|
|||||||
data: Record<string, unknown>; // workflow parameters and values
|
data: Record<string, unknown>; // workflow parameters and values
|
||||||
proxy_location: ProxyLocation | null;
|
proxy_location: ProxyLocation | null;
|
||||||
webhook_callback_url?: string | null;
|
webhook_callback_url?: string | null;
|
||||||
|
browser_session_id: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getRunWorkflowRequestBody(
|
function getRunWorkflowRequestBody(
|
||||||
values: RunWorkflowFormType,
|
values: RunWorkflowFormType,
|
||||||
workflowParameters: Array<WorkflowParameter>,
|
workflowParameters: Array<WorkflowParameter>,
|
||||||
): RunWorkflowRequestBody {
|
): RunWorkflowRequestBody {
|
||||||
const { webhookCallbackUrl, proxyLocation, ...parameters } = values;
|
const { webhookCallbackUrl, proxyLocation, browserSessionId, ...parameters } =
|
||||||
|
values;
|
||||||
|
|
||||||
const parsedParameters = parseValuesForWorkflowRun(
|
const parsedParameters = parseValuesForWorkflowRun(
|
||||||
parameters,
|
parameters,
|
||||||
workflowParameters,
|
workflowParameters,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const bsi = browserSessionId?.trim() === "" ? null : browserSessionId;
|
||||||
|
|
||||||
const body: RunWorkflowRequestBody = {
|
const body: RunWorkflowRequestBody = {
|
||||||
data: parsedParameters,
|
data: parsedParameters,
|
||||||
proxy_location: proxyLocation,
|
proxy_location: proxyLocation,
|
||||||
|
browser_session_id: bsi,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (webhookCallbackUrl) {
|
if (webhookCallbackUrl) {
|
||||||
@@ -98,6 +105,7 @@ function getRunWorkflowRequestBody(
|
|||||||
type RunWorkflowFormType = Record<string, unknown> & {
|
type RunWorkflowFormType = Record<string, unknown> & {
|
||||||
webhookCallbackUrl: string;
|
webhookCallbackUrl: string;
|
||||||
proxyLocation: ProxyLocation;
|
proxyLocation: ProxyLocation;
|
||||||
|
browserSessionId: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
function RunWorkflowForm({
|
function RunWorkflowForm({
|
||||||
@@ -109,15 +117,26 @@ function RunWorkflowForm({
|
|||||||
const credentialGetter = useCredentialGetter();
|
const credentialGetter = useCredentialGetter();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const browserSessionIdDefault = useLocalStorageFormDefault(
|
||||||
|
"skyvern.browserSessionId",
|
||||||
|
(initialValues.browserSessionId as string | undefined) ?? null,
|
||||||
|
);
|
||||||
const form = useForm<RunWorkflowFormType>({
|
const form = useForm<RunWorkflowFormType>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
...initialValues,
|
...initialValues,
|
||||||
webhookCallbackUrl: initialSettings.webhookCallbackUrl,
|
webhookCallbackUrl: initialSettings.webhookCallbackUrl,
|
||||||
proxyLocation: initialSettings.proxyLocation,
|
proxyLocation: initialSettings.proxyLocation,
|
||||||
|
browserSessionId: browserSessionIdDefault,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const apiCredential = useApiCredential();
|
const apiCredential = useApiCredential();
|
||||||
|
|
||||||
|
useSyncFormFieldToStorage(
|
||||||
|
form,
|
||||||
|
"browserSessionId",
|
||||||
|
"skyvern.browserSessionId",
|
||||||
|
);
|
||||||
|
|
||||||
const runWorkflowMutation = useMutation({
|
const runWorkflowMutation = useMutation({
|
||||||
mutationFn: async (values: RunWorkflowFormType) => {
|
mutationFn: async (values: RunWorkflowFormType) => {
|
||||||
const client = await getClient(credentialGetter);
|
const client = await getClient(credentialGetter);
|
||||||
@@ -154,7 +173,13 @@ function RunWorkflowForm({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function onSubmit(values: RunWorkflowFormType) {
|
function onSubmit(values: RunWorkflowFormType) {
|
||||||
const { webhookCallbackUrl, proxyLocation, ...parameters } = values;
|
const {
|
||||||
|
webhookCallbackUrl,
|
||||||
|
proxyLocation,
|
||||||
|
browserSessionId,
|
||||||
|
...parameters
|
||||||
|
} = values;
|
||||||
|
|
||||||
const parsedParameters = parseValuesForWorkflowRun(
|
const parsedParameters = parseValuesForWorkflowRun(
|
||||||
parameters,
|
parameters,
|
||||||
workflowParameters,
|
workflowParameters,
|
||||||
@@ -163,6 +188,7 @@ function RunWorkflowForm({
|
|||||||
...parsedParameters,
|
...parsedParameters,
|
||||||
webhookCallbackUrl,
|
webhookCallbackUrl,
|
||||||
proxyLocation,
|
proxyLocation,
|
||||||
|
browserSessionId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,8 +356,43 @@ function RunWorkflowForm({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
key="browserSessionId"
|
||||||
|
control={form.control}
|
||||||
|
name="browserSessionId"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<div className="flex gap-16">
|
||||||
|
<FormLabel>
|
||||||
|
<div className="w-72">
|
||||||
|
<div className="flex items-center gap-2 text-lg">
|
||||||
|
Browser Session ID
|
||||||
|
</div>
|
||||||
|
<h2 className="text-sm text-slate-400">
|
||||||
|
Use a persistent browser session to maintain state and
|
||||||
|
enable browser interaction.
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</FormLabel>
|
||||||
|
<div className="w-full space-y-2">
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="pbs_xxx"
|
||||||
|
value={
|
||||||
|
field.value === null ? "" : (field.value as string)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
<CopyApiCommandDropdown
|
<CopyApiCommandDropdown
|
||||||
getOptions={() => {
|
getOptions={() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user