add ai_fallback to TaskV2Request model (#3498)
This commit is contained in:
@@ -1,16 +1,9 @@
|
|||||||
interface AnimatedWaveProps {
|
interface AnimatedWaveProps {
|
||||||
text: string;
|
text: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
duration?: string;
|
|
||||||
waveHeight?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AnimatedWave({
|
export function AnimatedWave({ text, className = "" }: AnimatedWaveProps) {
|
||||||
text,
|
|
||||||
className = "",
|
|
||||||
duration = "1.3s",
|
|
||||||
waveHeight = "4px",
|
|
||||||
}: AnimatedWaveProps) {
|
|
||||||
const characters = text.split("");
|
const characters = text.split("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -21,7 +14,7 @@ export function AnimatedWave({
|
|||||||
transform: translateY(0px);
|
transform: translateY(0px);
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
transform: translateY(-${waveHeight});
|
transform: translateY(-4px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.animate-wave {
|
.animate-wave {
|
||||||
@@ -35,7 +28,7 @@ export function AnimatedWave({
|
|||||||
className="animate-wave inline-block"
|
className="animate-wave inline-block"
|
||||||
style={{
|
style={{
|
||||||
animationDelay: `${index * 0.1}s`,
|
animationDelay: `${index * 0.1}s`,
|
||||||
animationDuration: duration,
|
animationDuration: "1.3s",
|
||||||
animationIterationCount: "infinite",
|
animationIterationCount: "infinite",
|
||||||
animationTimingFunction: "ease-in-out",
|
animationTimingFunction: "ease-in-out",
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { NavLink, useSearchParams } from "react-router-dom";
|
|||||||
type Option = {
|
type Option = {
|
||||||
label: string;
|
label: string;
|
||||||
to: string;
|
to: string;
|
||||||
icon?: React.ReactNode;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -24,18 +23,13 @@ function SwitchBarNavigation({ options }: Props) {
|
|||||||
key={option.to}
|
key={option.to}
|
||||||
className={({ isActive }) => {
|
className={({ isActive }) => {
|
||||||
return cn(
|
return cn(
|
||||||
"flex cursor-pointer items-center justify-center rounded-sm px-3 py-2 text-center hover:bg-slate-700",
|
"cursor-pointer rounded-sm px-3 py-2 hover:bg-slate-700",
|
||||||
{
|
{
|
||||||
"bg-slate-700": isActive,
|
"bg-slate-700": isActive,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{option.icon && (
|
|
||||||
<span className="mr-1 flex items-center justify-center">
|
|
||||||
{option.icon}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{option.label}
|
{option.label}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
);
|
);
|
||||||
@@ -44,4 +38,4 @@ function SwitchBarNavigation({ options }: Props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { SwitchBarNavigation, type Option as SwitchBarNavigationOption };
|
export { SwitchBarNavigation };
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { getClient } from "@/api/AxiosClient";
|
import { getClient } from "@/api/AxiosClient";
|
||||||
import { ProxyLocation, Status } from "@/api/types";
|
import { ProxyLocation, Status } from "@/api/types";
|
||||||
import { StatusBadge } from "@/components/StatusBadge";
|
import { StatusBadge } from "@/components/StatusBadge";
|
||||||
import {
|
import { SwitchBarNavigation } from "@/components/SwitchBarNavigation";
|
||||||
SwitchBarNavigation,
|
|
||||||
type SwitchBarNavigationOption,
|
|
||||||
} from "@/components/SwitchBarNavigation";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -22,7 +19,6 @@ import { useApiCredential } from "@/hooks/useApiCredential";
|
|||||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
import { apiBaseUrl } from "@/util/env";
|
import { apiBaseUrl } from "@/util/env";
|
||||||
import {
|
import {
|
||||||
CodeIcon,
|
|
||||||
FileIcon,
|
FileIcon,
|
||||||
Pencil2Icon,
|
Pencil2Icon,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
@@ -65,8 +61,6 @@ function WorkflowRun() {
|
|||||||
isFetched,
|
isFetched,
|
||||||
} = useWorkflowRunQuery();
|
} = useWorkflowRunQuery();
|
||||||
|
|
||||||
const isFinalized = workflowRun ? statusIsFinalized(workflowRun) : null;
|
|
||||||
|
|
||||||
const { data: workflowRunTimeline } = useWorkflowRunTimelineQuery();
|
const { data: workflowRunTimeline } = useWorkflowRunTimelineQuery();
|
||||||
|
|
||||||
const cancelWorkflowMutation = useMutation({
|
const cancelWorkflowMutation = useMutation({
|
||||||
@@ -214,7 +208,7 @@ function WorkflowRun() {
|
|||||||
webhookFailureReasonData) &&
|
webhookFailureReasonData) &&
|
||||||
workflowRun.status === Status.Completed;
|
workflowRun.status === Status.Completed;
|
||||||
|
|
||||||
const switchBarOptions: SwitchBarNavigationOption[] = [
|
const switchBarOptions = [
|
||||||
{
|
{
|
||||||
label: "Overview",
|
label: "Overview",
|
||||||
to: "overview",
|
to: "overview",
|
||||||
@@ -233,18 +227,10 @@ function WorkflowRun() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const isGeneratingCode = !isFinalized && workflow?.generate_script === true;
|
|
||||||
|
|
||||||
if (!hasScript) {
|
if (!hasScript) {
|
||||||
switchBarOptions.push({
|
switchBarOptions.push({
|
||||||
label: "Code",
|
label: "Code",
|
||||||
to: "code",
|
to: "code",
|
||||||
icon:
|
|
||||||
isFinalized || !isGeneratingCode ? (
|
|
||||||
<CodeIcon className="inline-block size-5" />
|
|
||||||
) : (
|
|
||||||
<ReloadIcon className="inline-block size-5 animate-spin" />
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import {
|
|||||||
import { Flippable } from "@/components/Flippable";
|
import { Flippable } from "@/components/Flippable";
|
||||||
import { useRerender } from "@/hooks/useRerender";
|
import { useRerender } from "@/hooks/useRerender";
|
||||||
import { useBlockScriptStore } from "@/store/BlockScriptStore";
|
import { useBlockScriptStore } from "@/store/BlockScriptStore";
|
||||||
import { useUiStore } from "@/store/UiStore";
|
|
||||||
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
|
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
|
||||||
import { cn } from "@/util/utils";
|
import { cn } from "@/util/utils";
|
||||||
|
|
||||||
@@ -79,31 +78,11 @@ function StartNode({ id, data }: NodeProps<StartNode>) {
|
|||||||
runSequentially: data.withWorkflowSettings ? data.runSequentially : false,
|
runSequentially: data.withWorkflowSettings ? data.runSequentially : false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { highlightGenerateCodeToggle, setHighlightGenerateCodeToggle } =
|
|
||||||
useUiStore();
|
|
||||||
const [facing, setFacing] = useState<"front" | "back">("front");
|
const [facing, setFacing] = useState<"front" | "back">("front");
|
||||||
const blockScriptStore = useBlockScriptStore();
|
const blockScriptStore = useBlockScriptStore();
|
||||||
const script = blockScriptStore.scripts.__start_block__;
|
const script = blockScriptStore.scripts.__start_block__;
|
||||||
const rerender = useRerender({ prefix: "accordion" });
|
const rerender = useRerender({ prefix: "accordion" });
|
||||||
const toggleScriptForNodeCallback = useToggleScriptForNodeCallback();
|
const toggleScriptForNodeCallback = useToggleScriptForNodeCallback();
|
||||||
const [expandWorkflowSettings, setExpandWorkflowSettings] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const tm = setTimeout(() => {
|
|
||||||
if (highlightGenerateCodeToggle) {
|
|
||||||
setExpandWorkflowSettings(true);
|
|
||||||
rerender.bump();
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
setHighlightGenerateCodeToggle(false);
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
|
|
||||||
return () => clearTimeout(tm);
|
|
||||||
// onMount only
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFacing(data.showCode ? "back" : "front");
|
setFacing(data.showCode ? "back" : "front");
|
||||||
@@ -158,10 +137,6 @@ function StartNode({ id, data }: NodeProps<StartNode>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultWorkflowSettings = expandWorkflowSettings
|
|
||||||
? "settings"
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (data.withWorkflowSettings) {
|
if (data.withWorkflowSettings) {
|
||||||
return (
|
return (
|
||||||
<Flippable facing={facing} preserveFrontsideHeight={true}>
|
<Flippable facing={facing} preserveFrontsideHeight={true}>
|
||||||
@@ -184,12 +159,7 @@ function StartNode({ id, data }: NodeProps<StartNode>) {
|
|||||||
<Accordion
|
<Accordion
|
||||||
type="single"
|
type="single"
|
||||||
collapsible
|
collapsible
|
||||||
value={defaultWorkflowSettings}
|
onValueChange={() => rerender.bump()}
|
||||||
defaultValue={defaultWorkflowSettings}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
setExpandWorkflowSettings(value === "settings");
|
|
||||||
rerender.bump();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<AccordionItem value="settings" className="mt-4 border-b-0">
|
<AccordionItem value="settings" className="mt-4 border-b-0">
|
||||||
<AccordionTrigger className="py-2">
|
<AccordionTrigger className="py-2">
|
||||||
@@ -237,16 +207,10 @@ function StartNode({ id, data }: NodeProps<StartNode>) {
|
|||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div
|
<div className="flex items-center gap-2">
|
||||||
className={cn("flex items-center gap-2", {
|
<Label>Run Cached Code</Label>
|
||||||
"animate-pulse rounded-md bg-yellow-600/20":
|
|
||||||
highlightGenerateCodeToggle,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Label>Generate Code</Label>
|
|
||||||
<HelpTooltip content="If code has been cached, run the workflow using code for faster execution." />
|
<HelpTooltip content="If code has been cached, run the workflow using code for faster execution." />
|
||||||
<Switch
|
<Switch
|
||||||
disabled={inputs.useScriptCache === true} // TODO(jdo/always-generate): remove
|
|
||||||
className="ml-auto"
|
className="ml-auto"
|
||||||
checked={inputs.useScriptCache}
|
checked={inputs.useScriptCache}
|
||||||
onCheckedChange={(value) => {
|
onCheckedChange={(value) => {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ type Props = {
|
|||||||
cacheKeyValue?: string;
|
cacheKeyValue?: string;
|
||||||
workflowPermanentId?: string;
|
workflowPermanentId?: string;
|
||||||
pollIntervalMs?: number;
|
pollIntervalMs?: number;
|
||||||
status?: "pending" | "published";
|
status?: string;
|
||||||
workflowRunId?: string;
|
workflowRunId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ExclamationTriangleIcon, ReloadIcon } from "@radix-ui/react-icons";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
@@ -20,7 +19,6 @@ import { useWorkflowQuery } from "@/routes/workflows/hooks/useWorkflowQuery";
|
|||||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||||
import { constructCacheKeyValue } from "@/routes/workflows/editor/utils";
|
import { constructCacheKeyValue } from "@/routes/workflows/editor/utils";
|
||||||
import { getCode, getOrderedBlockLabels } from "@/routes/workflows/utils";
|
import { getCode, getOrderedBlockLabels } from "@/routes/workflows/utils";
|
||||||
import { useUiStore } from "@/store/UiStore";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
showCacheKeyValueSelector?: boolean;
|
showCacheKeyValueSelector?: boolean;
|
||||||
@@ -46,7 +44,6 @@ function WorkflowRunCode(props?: Props) {
|
|||||||
page: 1,
|
page: 1,
|
||||||
workflowPermanentId,
|
workflowPermanentId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isFinalized = workflowRun ? statusIsFinalized(workflowRun) : null;
|
const isFinalized = workflowRun ? statusIsFinalized(workflowRun) : null;
|
||||||
const parameters = workflowRun?.parameters;
|
const parameters = workflowRun?.parameters;
|
||||||
|
|
||||||
@@ -55,17 +52,11 @@ function WorkflowRunCode(props?: Props) {
|
|||||||
cacheKeyValue,
|
cacheKeyValue,
|
||||||
workflowPermanentId,
|
workflowPermanentId,
|
||||||
pollIntervalMs: !isFinalized ? 3000 : undefined,
|
pollIntervalMs: !isFinalized ? 3000 : undefined,
|
||||||
status: isFinalized ? "published" : "pending",
|
status: "pending",
|
||||||
workflowRunId: workflowRun?.workflow_run_id,
|
workflowRunId: workflowRun?.workflow_run_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const orderedBlockLabels = getOrderedBlockLabels(workflow);
|
const orderedBlockLabels = getOrderedBlockLabels(workflow);
|
||||||
const code = getCode(orderedBlockLabels, blockScripts).join("").trim();
|
const code = getCode(orderedBlockLabels, blockScripts).join("").trim();
|
||||||
const isGeneratingCode = !isFinalized && workflow?.generate_script === true;
|
|
||||||
const couldBeGeneratingCode =
|
|
||||||
!isFinalized && workflow?.generate_script !== true;
|
|
||||||
|
|
||||||
const { setHighlightGenerateCodeToggle } = useUiStore();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCacheKeyValue(
|
setCacheKeyValue(
|
||||||
@@ -102,7 +93,7 @@ function WorkflowRunCode(props?: Props) {
|
|||||||
});
|
});
|
||||||
}, [queryClient, workflowRun, workflowPermanentId, cacheKey, cacheKeyValue]);
|
}, [queryClient, workflowRun, workflowPermanentId, cacheKey, cacheKeyValue]);
|
||||||
|
|
||||||
if (code.length === 0 && isFinalized) {
|
if (code.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center bg-slate-elevation3 p-8">
|
<div className="flex items-center justify-center bg-slate-elevation3 p-8">
|
||||||
No code has been generated yet.
|
No code has been generated yet.
|
||||||
@@ -110,6 +101,19 @@ function WorkflowRunCode(props?: Props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!showCacheKeyValueSelector || !cacheKey || cacheKey === "") {
|
||||||
|
return (
|
||||||
|
<CodeEditor
|
||||||
|
className="h-full overflow-y-scroll"
|
||||||
|
language="python"
|
||||||
|
value={code}
|
||||||
|
lineWrap={false}
|
||||||
|
readOnly
|
||||||
|
fontSize={10}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const cacheKeyValueSet = new Set([...(cacheKeyValues?.values ?? [])]);
|
const cacheKeyValueSet = new Set([...(cacheKeyValues?.values ?? [])]);
|
||||||
|
|
||||||
const cacheKeyValueForWorkflowRun = constructCacheKeyValue({
|
const cacheKeyValueForWorkflowRun = constructCacheKeyValue({
|
||||||
@@ -123,87 +127,52 @@ function WorkflowRunCode(props?: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-col items-end justify-start gap-2">
|
<div className="flex h-full w-full flex-col items-end justify-center gap-2">
|
||||||
{isGeneratingCode && (
|
<div className="flex w-full justify-end gap-4">
|
||||||
<div className="mb-6 flex w-full gap-2 rounded-md border-[1px] border-[slate-300] p-2">
|
<div className="flex items-center justify-around gap-2">
|
||||||
<div className="p6 flex w-full items-center justify-center rounded-l-md bg-slate-elevation5 px-4 py-2 text-sm">
|
<Label className="w-[7rem]">Code Key Value</Label>
|
||||||
Generating code...
|
<HelpTooltip
|
||||||
</div>
|
content={
|
||||||
<div className="p6 flex items-center justify-center rounded-r-md bg-slate-elevation5 px-4 py-2 text-sm">
|
!isFinalized
|
||||||
<ReloadIcon className="size-8 animate-spin" />
|
? "The code key value the generated code is being stored under."
|
||||||
</div>
|
: "Which generated (& cached) code to view."
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<Select
|
||||||
{couldBeGeneratingCode && (
|
disabled={!isFinalized}
|
||||||
<div className="mb-6 flex w-full gap-2 rounded-md border-[1px] border-[slate-300] p-2">
|
value={cacheKeyValue}
|
||||||
<div className="flex w-full items-center justify-center gap-2 rounded-l-md text-sm">
|
onValueChange={(v: string) => setCacheKeyValue(v)}
|
||||||
<div className="flex-1 bg-slate-elevation5 p-4">
|
>
|
||||||
Code generation disabled for this run. Please enable{" "}
|
<SelectTrigger className="max-w-[15rem] [&>span]:text-ellipsis">
|
||||||
<a
|
<SelectValue placeholder="Code Key Value" />
|
||||||
className="underline hover:text-sky-500"
|
</SelectTrigger>
|
||||||
href={`${location.origin}/workflows/${workflowPermanentId}/debug`}
|
<SelectContent>
|
||||||
target="_blank"
|
{Array.from(cacheKeyValueSet)
|
||||||
onClick={() => setHighlightGenerateCodeToggle(true)}
|
.sort()
|
||||||
>
|
.map((value) => {
|
||||||
Generate Code
|
return (
|
||||||
</a>{" "}
|
<SelectItem key={value} value={value}>
|
||||||
in your Workflow Settings to have Skyvern generate code.
|
{value === cacheKeyValueForWorkflowRun &&
|
||||||
</div>
|
isFinalized === true ? (
|
||||||
</div>
|
<span className="underline">{value}</span>
|
||||||
<div className="p6 flex items-center justify-center rounded-r-md bg-slate-elevation5 px-4 py-2 text-sm">
|
) : (
|
||||||
<ExclamationTriangleIcon className="size-8 text-[gold]" />
|
value
|
||||||
</div>
|
)}
|
||||||
</div>
|
</SelectItem>
|
||||||
)}
|
);
|
||||||
{showCacheKeyValueSelector && cacheKey && cacheKey !== "" && (
|
})}
|
||||||
<div className="flex w-full justify-end gap-4">
|
</SelectContent>
|
||||||
<div className="flex items-center justify-around gap-2">
|
</Select>
|
||||||
<Label className="w-[7rem]">Code Key Value</Label>
|
</div>
|
||||||
<HelpTooltip
|
<CodeEditor
|
||||||
content={
|
className="h-full w-full overflow-y-scroll"
|
||||||
!isFinalized
|
language="python"
|
||||||
? "The code key value the generated code is being stored under."
|
value={code}
|
||||||
: "Which generated (& cached) code to view."
|
lineWrap={false}
|
||||||
}
|
readOnly
|
||||||
/>
|
fontSize={10}
|
||||||
</div>
|
/>
|
||||||
<Select
|
|
||||||
disabled={!isFinalized}
|
|
||||||
value={cacheKeyValue}
|
|
||||||
onValueChange={(v: string) => setCacheKeyValue(v)}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="max-w-[15rem] [&>span]:text-ellipsis">
|
|
||||||
<SelectValue placeholder="Code Key Value" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{Array.from(cacheKeyValueSet)
|
|
||||||
.sort()
|
|
||||||
.map((value) => {
|
|
||||||
return (
|
|
||||||
<SelectItem key={value} value={value}>
|
|
||||||
{value === cacheKeyValueForWorkflowRun &&
|
|
||||||
isFinalized === true ? (
|
|
||||||
<span className="underline">{value}</span>
|
|
||||||
) : (
|
|
||||||
value
|
|
||||||
)}
|
|
||||||
</SelectItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{(isGeneratingCode || (code && code.length > 0)) && (
|
|
||||||
<CodeEditor
|
|
||||||
className="h-full w-full overflow-y-scroll"
|
|
||||||
language="python"
|
|
||||||
value={code}
|
|
||||||
lineWrap={false}
|
|
||||||
readOnly
|
|
||||||
fontSize={10}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
/**
|
|
||||||
* UI Store: put UI-only state here, that needs to be shared across components, tabs, and
|
|
||||||
* potentially browser refreshes.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { create } from "zustand";
|
|
||||||
|
|
||||||
const namespace = "skyvern.ui" as const;
|
|
||||||
|
|
||||||
const write = (key: string, value: unknown) => {
|
|
||||||
try {
|
|
||||||
const serialized = JSON.stringify(value);
|
|
||||||
localStorage.setItem(makeKey(key), serialized);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error writing to localStorage:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const read = <T>(
|
|
||||||
key: string,
|
|
||||||
validator: (v: T) => boolean,
|
|
||||||
defaultValue: T,
|
|
||||||
): T => {
|
|
||||||
try {
|
|
||||||
const serialized = localStorage.getItem(makeKey(key));
|
|
||||||
|
|
||||||
if (serialized === null) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = JSON.parse(serialized) as T;
|
|
||||||
|
|
||||||
if (validator(value)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue;
|
|
||||||
} catch (error) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const makeKey = (name: string) => {
|
|
||||||
return `${namespace}.${name}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
type UiStore = {
|
|
||||||
highlightGenerateCodeToggle: boolean;
|
|
||||||
setHighlightGenerateCodeToggle: (v: boolean) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* There's gotta be a way to remove this boilerplate and keep type-safety (no time)...
|
|
||||||
*/
|
|
||||||
const useUiStore = create<UiStore>((set) => {
|
|
||||||
return {
|
|
||||||
highlightGenerateCodeToggle: read(
|
|
||||||
makeKey("highlightGenerateCodeToggle"),
|
|
||||||
(v) => typeof v === "boolean",
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
setHighlightGenerateCodeToggle: (v: boolean) => {
|
|
||||||
set({ highlightGenerateCodeToggle: v });
|
|
||||||
write(makeKey("highlightGenerateCodeToggle"), v);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export { useUiStore };
|
|
||||||
@@ -157,6 +157,7 @@ class TaskV2Request(BaseModel):
|
|||||||
extra_http_headers: dict[str, str] | None = None
|
extra_http_headers: dict[str, str] | None = None
|
||||||
browser_address: str | None = None
|
browser_address: str | None = None
|
||||||
generate_script: bool = False
|
generate_script: bool = False
|
||||||
|
ai_fallback: bool = False
|
||||||
|
|
||||||
@field_validator("url", "webhook_callback_url", "totp_verification_url")
|
@field_validator("url", "webhook_callback_url", "totp_verification_url")
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
Reference in New Issue
Block a user