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 { useApiCredential } from "@/hooks/useApiCredential";
|
||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||
import { useSyncFormFieldToStorage } from "@/hooks/useSyncFormFieldToStorage";
|
||||
import { useLocalStorageFormDefault } from "@/hooks/useLocalStorageFormDefault";
|
||||
import { apiBaseUrl } from "@/util/env";
|
||||
import { type ApiCommandOptions } from "@/util/apiCommands";
|
||||
import { PlayIcon, ReloadIcon } from "@radix-ui/react-icons";
|
||||
@@ -70,22 +72,27 @@ type RunWorkflowRequestBody = {
|
||||
data: Record<string, unknown>; // workflow parameters and values
|
||||
proxy_location: ProxyLocation | null;
|
||||
webhook_callback_url?: string | null;
|
||||
browser_session_id: string | null;
|
||||
};
|
||||
|
||||
function getRunWorkflowRequestBody(
|
||||
values: RunWorkflowFormType,
|
||||
workflowParameters: Array<WorkflowParameter>,
|
||||
): RunWorkflowRequestBody {
|
||||
const { webhookCallbackUrl, proxyLocation, ...parameters } = values;
|
||||
const { webhookCallbackUrl, proxyLocation, browserSessionId, ...parameters } =
|
||||
values;
|
||||
|
||||
const parsedParameters = parseValuesForWorkflowRun(
|
||||
parameters,
|
||||
workflowParameters,
|
||||
);
|
||||
|
||||
const bsi = browserSessionId?.trim() === "" ? null : browserSessionId;
|
||||
|
||||
const body: RunWorkflowRequestBody = {
|
||||
data: parsedParameters,
|
||||
proxy_location: proxyLocation,
|
||||
browser_session_id: bsi,
|
||||
};
|
||||
|
||||
if (webhookCallbackUrl) {
|
||||
@@ -98,6 +105,7 @@ function getRunWorkflowRequestBody(
|
||||
type RunWorkflowFormType = Record<string, unknown> & {
|
||||
webhookCallbackUrl: string;
|
||||
proxyLocation: ProxyLocation;
|
||||
browserSessionId: string | null;
|
||||
};
|
||||
|
||||
function RunWorkflowForm({
|
||||
@@ -109,15 +117,26 @@ function RunWorkflowForm({
|
||||
const credentialGetter = useCredentialGetter();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const browserSessionIdDefault = useLocalStorageFormDefault(
|
||||
"skyvern.browserSessionId",
|
||||
(initialValues.browserSessionId as string | undefined) ?? null,
|
||||
);
|
||||
const form = useForm<RunWorkflowFormType>({
|
||||
defaultValues: {
|
||||
...initialValues,
|
||||
webhookCallbackUrl: initialSettings.webhookCallbackUrl,
|
||||
proxyLocation: initialSettings.proxyLocation,
|
||||
browserSessionId: browserSessionIdDefault,
|
||||
},
|
||||
});
|
||||
const apiCredential = useApiCredential();
|
||||
|
||||
useSyncFormFieldToStorage(
|
||||
form,
|
||||
"browserSessionId",
|
||||
"skyvern.browserSessionId",
|
||||
);
|
||||
|
||||
const runWorkflowMutation = useMutation({
|
||||
mutationFn: async (values: RunWorkflowFormType) => {
|
||||
const client = await getClient(credentialGetter);
|
||||
@@ -154,7 +173,13 @@ function RunWorkflowForm({
|
||||
});
|
||||
|
||||
function onSubmit(values: RunWorkflowFormType) {
|
||||
const { webhookCallbackUrl, proxyLocation, ...parameters } = values;
|
||||
const {
|
||||
webhookCallbackUrl,
|
||||
proxyLocation,
|
||||
browserSessionId,
|
||||
...parameters
|
||||
} = values;
|
||||
|
||||
const parsedParameters = parseValuesForWorkflowRun(
|
||||
parameters,
|
||||
workflowParameters,
|
||||
@@ -163,6 +188,7 @@ function RunWorkflowForm({
|
||||
...parsedParameters,
|
||||
webhookCallbackUrl,
|
||||
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 className="flex justify-end gap-2">
|
||||
<CopyApiCommandDropdown
|
||||
getOptions={() => {
|
||||
|
||||
Reference in New Issue
Block a user