From 1a657b158742b80dd03ef738bf021bd3e7502054 Mon Sep 17 00:00:00 2001 From: Jonathan Dobson Date: Mon, 22 Sep 2025 11:13:06 -0400 Subject: [PATCH] add ai_fallback to TaskV2Request model (#3498) --- .../src/components/AnimatedWave.tsx | 13 +- .../src/components/SwitchBarNavigation.tsx | 10 +- .../src/routes/workflows/WorkflowRun.tsx | 18 +-- .../editor/nodes/StartNode/StartNode.tsx | 42 +---- .../workflows/hooks/useBlockScriptsQuery.ts | 2 +- .../workflows/workflowRun/WorkflowRunCode.tsx | 151 +++++++----------- skyvern-frontend/src/store/UiStore.ts | 69 -------- skyvern/forge/sdk/schemas/task_v2.py | 1 + 8 files changed, 72 insertions(+), 234 deletions(-) delete mode 100644 skyvern-frontend/src/store/UiStore.ts diff --git a/skyvern-frontend/src/components/AnimatedWave.tsx b/skyvern-frontend/src/components/AnimatedWave.tsx index f44e0ff0..5229aca7 100644 --- a/skyvern-frontend/src/components/AnimatedWave.tsx +++ b/skyvern-frontend/src/components/AnimatedWave.tsx @@ -1,16 +1,9 @@ interface AnimatedWaveProps { text: string; className?: string; - duration?: string; - waveHeight?: string; } -export function AnimatedWave({ - text, - className = "", - duration = "1.3s", - waveHeight = "4px", -}: AnimatedWaveProps) { +export function AnimatedWave({ text, className = "" }: AnimatedWaveProps) { const characters = text.split(""); return ( @@ -21,7 +14,7 @@ export function AnimatedWave({ transform: translateY(0px); } 50% { - transform: translateY(-${waveHeight}); + transform: translateY(-4px); } } .animate-wave { @@ -35,7 +28,7 @@ export function AnimatedWave({ className="animate-wave inline-block" style={{ animationDelay: `${index * 0.1}s`, - animationDuration: duration, + animationDuration: "1.3s", animationIterationCount: "infinite", animationTimingFunction: "ease-in-out", }} diff --git a/skyvern-frontend/src/components/SwitchBarNavigation.tsx b/skyvern-frontend/src/components/SwitchBarNavigation.tsx index 26587072..c7943184 100644 --- a/skyvern-frontend/src/components/SwitchBarNavigation.tsx +++ b/skyvern-frontend/src/components/SwitchBarNavigation.tsx @@ -4,7 +4,6 @@ import { NavLink, useSearchParams } from "react-router-dom"; type Option = { label: string; to: string; - icon?: React.ReactNode; }; type Props = { @@ -24,18 +23,13 @@ function SwitchBarNavigation({ options }: Props) { key={option.to} className={({ isActive }) => { 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, }, ); }} > - {option.icon && ( - - {option.icon} - - )} {option.label} ); @@ -44,4 +38,4 @@ function SwitchBarNavigation({ options }: Props) { ); } -export { SwitchBarNavigation, type Option as SwitchBarNavigationOption }; +export { SwitchBarNavigation }; diff --git a/skyvern-frontend/src/routes/workflows/WorkflowRun.tsx b/skyvern-frontend/src/routes/workflows/WorkflowRun.tsx index fcb50b5e..d25b62d8 100644 --- a/skyvern-frontend/src/routes/workflows/WorkflowRun.tsx +++ b/skyvern-frontend/src/routes/workflows/WorkflowRun.tsx @@ -1,10 +1,7 @@ import { getClient } from "@/api/AxiosClient"; import { ProxyLocation, Status } from "@/api/types"; import { StatusBadge } from "@/components/StatusBadge"; -import { - SwitchBarNavigation, - type SwitchBarNavigationOption, -} from "@/components/SwitchBarNavigation"; +import { SwitchBarNavigation } from "@/components/SwitchBarNavigation"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -22,7 +19,6 @@ import { useApiCredential } from "@/hooks/useApiCredential"; import { useCredentialGetter } from "@/hooks/useCredentialGetter"; import { apiBaseUrl } from "@/util/env"; import { - CodeIcon, FileIcon, Pencil2Icon, PlayIcon, @@ -65,8 +61,6 @@ function WorkflowRun() { isFetched, } = useWorkflowRunQuery(); - const isFinalized = workflowRun ? statusIsFinalized(workflowRun) : null; - const { data: workflowRunTimeline } = useWorkflowRunTimelineQuery(); const cancelWorkflowMutation = useMutation({ @@ -214,7 +208,7 @@ function WorkflowRun() { webhookFailureReasonData) && workflowRun.status === Status.Completed; - const switchBarOptions: SwitchBarNavigationOption[] = [ + const switchBarOptions = [ { label: "Overview", to: "overview", @@ -233,18 +227,10 @@ function WorkflowRun() { }, ]; - const isGeneratingCode = !isFinalized && workflow?.generate_script === true; - if (!hasScript) { switchBarOptions.push({ label: "Code", to: "code", - icon: - isFinalized || !isGeneratingCode ? ( - - ) : ( - - ), }); } diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/StartNode/StartNode.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/StartNode/StartNode.tsx index 02604970..235c0b89 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/StartNode/StartNode.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/StartNode/StartNode.tsx @@ -33,7 +33,6 @@ import { import { Flippable } from "@/components/Flippable"; import { useRerender } from "@/hooks/useRerender"; import { useBlockScriptStore } from "@/store/BlockScriptStore"; -import { useUiStore } from "@/store/UiStore"; import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor"; import { cn } from "@/util/utils"; @@ -79,31 +78,11 @@ function StartNode({ id, data }: NodeProps) { runSequentially: data.withWorkflowSettings ? data.runSequentially : false, }); - const { highlightGenerateCodeToggle, setHighlightGenerateCodeToggle } = - useUiStore(); const [facing, setFacing] = useState<"front" | "back">("front"); const blockScriptStore = useBlockScriptStore(); const script = blockScriptStore.scripts.__start_block__; const rerender = useRerender({ prefix: "accordion" }); 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(() => { setFacing(data.showCode ? "back" : "front"); @@ -158,10 +137,6 @@ function StartNode({ id, data }: NodeProps) { } } - const defaultWorkflowSettings = expandWorkflowSettings - ? "settings" - : undefined; - if (data.withWorkflowSettings) { return ( @@ -184,12 +159,7 @@ function StartNode({ id, data }: NodeProps) { { - setExpandWorkflowSettings(value === "settings"); - rerender.bump(); - }} + onValueChange={() => rerender.bump()} > @@ -237,16 +207,10 @@ function StartNode({ id, data }: NodeProps) {
-
- +
+ { diff --git a/skyvern-frontend/src/routes/workflows/hooks/useBlockScriptsQuery.ts b/skyvern-frontend/src/routes/workflows/hooks/useBlockScriptsQuery.ts index abee0600..d22b15f9 100644 --- a/skyvern-frontend/src/routes/workflows/hooks/useBlockScriptsQuery.ts +++ b/skyvern-frontend/src/routes/workflows/hooks/useBlockScriptsQuery.ts @@ -8,7 +8,7 @@ type Props = { cacheKeyValue?: string; workflowPermanentId?: string; pollIntervalMs?: number; - status?: "pending" | "published"; + status?: string; workflowRunId?: string; }; diff --git a/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowRunCode.tsx b/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowRunCode.tsx index b1e1defd..a6ea0a6f 100644 --- a/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowRunCode.tsx +++ b/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowRunCode.tsx @@ -1,4 +1,3 @@ -import { ExclamationTriangleIcon, ReloadIcon } from "@radix-ui/react-icons"; import { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; 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 { constructCacheKeyValue } from "@/routes/workflows/editor/utils"; import { getCode, getOrderedBlockLabels } from "@/routes/workflows/utils"; -import { useUiStore } from "@/store/UiStore"; interface Props { showCacheKeyValueSelector?: boolean; @@ -46,7 +44,6 @@ function WorkflowRunCode(props?: Props) { page: 1, workflowPermanentId, }); - const isFinalized = workflowRun ? statusIsFinalized(workflowRun) : null; const parameters = workflowRun?.parameters; @@ -55,17 +52,11 @@ function WorkflowRunCode(props?: Props) { cacheKeyValue, workflowPermanentId, pollIntervalMs: !isFinalized ? 3000 : undefined, - status: isFinalized ? "published" : "pending", + status: "pending", workflowRunId: workflowRun?.workflow_run_id, }); - const orderedBlockLabels = getOrderedBlockLabels(workflow); 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(() => { setCacheKeyValue( @@ -102,7 +93,7 @@ function WorkflowRunCode(props?: Props) { }); }, [queryClient, workflowRun, workflowPermanentId, cacheKey, cacheKeyValue]); - if (code.length === 0 && isFinalized) { + if (code.length === 0) { return (
No code has been generated yet. @@ -110,6 +101,19 @@ function WorkflowRunCode(props?: Props) { ); } + if (!showCacheKeyValueSelector || !cacheKey || cacheKey === "") { + return ( + + ); + } + const cacheKeyValueSet = new Set([...(cacheKeyValues?.values ?? [])]); const cacheKeyValueForWorkflowRun = constructCacheKeyValue({ @@ -123,87 +127,52 @@ function WorkflowRunCode(props?: Props) { } return ( -
- {isGeneratingCode && ( -
-
- Generating code... -
-
- -
+
+
+
+ +
- )} - {couldBeGeneratingCode && ( -
-
-
- Code generation disabled for this run. Please enable{" "} - setHighlightGenerateCodeToggle(true)} - > - Generate Code - {" "} - in your Workflow Settings to have Skyvern generate code. -
-
-
- -
-
- )} - {showCacheKeyValueSelector && cacheKey && cacheKey !== "" && ( -
-
- - -
- -
- )} - {(isGeneratingCode || (code && code.length > 0)) && ( - - )} + +
+
); } diff --git a/skyvern-frontend/src/store/UiStore.ts b/skyvern-frontend/src/store/UiStore.ts deleted file mode 100644 index 117742b8..00000000 --- a/skyvern-frontend/src/store/UiStore.ts +++ /dev/null @@ -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 = ( - 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((set) => { - return { - highlightGenerateCodeToggle: read( - makeKey("highlightGenerateCodeToggle"), - (v) => typeof v === "boolean", - false, - ), - setHighlightGenerateCodeToggle: (v: boolean) => { - set({ highlightGenerateCodeToggle: v }); - write(makeKey("highlightGenerateCodeToggle"), v); - }, - }; -}); - -export { useUiStore }; diff --git a/skyvern/forge/sdk/schemas/task_v2.py b/skyvern/forge/sdk/schemas/task_v2.py index e0c09b43..d162929f 100644 --- a/skyvern/forge/sdk/schemas/task_v2.py +++ b/skyvern/forge/sdk/schemas/task_v2.py @@ -157,6 +157,7 @@ class TaskV2Request(BaseModel): extra_http_headers: dict[str, str] | None = None browser_address: str | None = None generate_script: bool = False + ai_fallback: bool = False @field_validator("url", "webhook_callback_url", "totp_verification_url") @classmethod