diff --git a/skyvern-frontend/src/components/WorkflowBlockInputSet.tsx b/skyvern-frontend/src/components/WorkflowBlockInputSet.tsx index 587b39a8..875a3e6c 100644 --- a/skyvern-frontend/src/components/WorkflowBlockInputSet.tsx +++ b/skyvern-frontend/src/components/WorkflowBlockInputSet.tsx @@ -1,11 +1,10 @@ import { PlusIcon } from "@radix-ui/react-icons"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { WorkflowBlockParameterSelect } from "@/routes/workflows/editor/nodes/WorkflowBlockParameterSelect"; +import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore"; import { useState, useEffect } from "react"; import { Cross2Icon } from "@radix-ui/react-icons"; import "./workflow-block-input-set.css"; -import { useWorkflowParametersState } from "@/routes/workflows/editor/useWorkflowParametersState"; - type Props = { onChange: (parameterKeys: Set) => void; nodeId: string; @@ -16,7 +15,7 @@ function WorkflowBlockInputSet(props: Props) { const { nodeId, onChange, values } = props; const [parameterKeys, setParameterKeys] = useState>(values); const hasKeys = parameterKeys.size > 0; - const [workflowParameters] = useWorkflowParametersState(); + const { parameters: workflowParameters } = useWorkflowParametersStore(); const availableParameterKeys = new Set( workflowParameters.map((parameter) => parameter.key), ); diff --git a/skyvern-frontend/src/routes/workflows/components/CredentialParameterSourceSelector.tsx b/skyvern-frontend/src/routes/workflows/components/CredentialParameterSourceSelector.tsx index 0d05b2d6..82a3cf8c 100644 --- a/skyvern-frontend/src/routes/workflows/components/CredentialParameterSourceSelector.tsx +++ b/skyvern-frontend/src/routes/workflows/components/CredentialParameterSourceSelector.tsx @@ -7,7 +7,7 @@ import { } from "@/components/ui/select"; import { Skeleton } from "@/components/ui/skeleton"; import { useCredentialsQuery } from "../hooks/useCredentialsQuery"; -import { useWorkflowParametersState } from "../editor/useWorkflowParametersState"; +import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore"; import { WorkflowParameterValueType } from "../types/workflowTypes"; import { PlusIcon } from "@radix-ui/react-icons"; import { @@ -24,7 +24,7 @@ type Props = { function CredentialParameterSourceSelector({ value, onChange }: Props) { const { data: credentials, isFetching } = useCredentialsQuery(); const { setIsOpen, setType } = useCredentialModalState(); - const [workflowParameters] = useWorkflowParametersState(); + const { parameters: workflowParameters } = useWorkflowParametersStore(); const workflowParametersOfTypeCredentialId = workflowParameters.filter( (parameter) => parameter.parameterType === "workflow" && diff --git a/skyvern-frontend/src/routes/workflows/components/SourceParameterKeySelector.tsx b/skyvern-frontend/src/routes/workflows/components/SourceParameterKeySelector.tsx index e26de241..90d8ebd3 100644 --- a/skyvern-frontend/src/routes/workflows/components/SourceParameterKeySelector.tsx +++ b/skyvern-frontend/src/routes/workflows/components/SourceParameterKeySelector.tsx @@ -1,5 +1,5 @@ import { useNodes } from "@xyflow/react"; -import { useWorkflowParametersState } from "../editor/useWorkflowParametersState"; +import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore"; import { AppNode, isWorkflowBlockNode } from "../editor/nodes"; import { getOutputParameterKey } from "../editor/workflowEditorUtils"; import { @@ -16,7 +16,7 @@ type Props = { }; function SourceParameterKeySelector({ value, onChange }: Props) { - const [workflowParameters] = useWorkflowParametersState(); + const { parameters: workflowParameters } = useWorkflowParametersStore(); const nodes = useNodes(); const contextParameterKeys = workflowParameters .filter((parameter) => parameter.parameterType !== "credential") diff --git a/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx b/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx index 37ebde26..8018624a 100644 --- a/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx @@ -16,6 +16,7 @@ import { useWorkflowSave, type WorkflowSaveData, } from "@/store/WorkflowHasChangesStore"; +import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore"; import { useWorkflowTitleStore } from "@/store/WorkflowTitleStore"; import { ReloadIcon } from "@radix-ui/react-icons"; import { @@ -223,7 +224,7 @@ type Props = { onNodesChange: (changes: Array>) => void; onEdgesChange: (changes: Array) => void; initialTitle: string; - initialParameters: ParametersState; + // initialParameters: ParametersState; workflow: WorkflowApiResponse; onDebuggableBlockCountChange: (count: number) => void; onMouseDownCapture?: () => void; @@ -238,7 +239,7 @@ function FlowRenderer({ onNodesChange, onEdgesChange, initialTitle, - initialParameters, + // initialParameters, workflow, onDebuggableBlockCountChange, onMouseDownCapture, @@ -247,7 +248,8 @@ function FlowRenderer({ const reactFlowInstance = useReactFlow(); const debugStore = useDebugStore(); const { title, initializeTitle } = useWorkflowTitleStore(); - const [parameters] = useState(initialParameters); + // const [parameters] = useState(initialParameters); + const parameters = useWorkflowParametersStore((state) => state.parameters); const nodesInitialized = useNodesInitialized(); const [shouldConstrainPan, setShouldConstrainPan] = useState(false); const onNodesChangeTimeoutRef = useRef(null); diff --git a/skyvern-frontend/src/routes/workflows/editor/WorkflowDebugger.tsx b/skyvern-frontend/src/routes/workflows/editor/WorkflowDebugger.tsx index b49c99c9..a82c0cd2 100644 --- a/skyvern-frontend/src/routes/workflows/editor/WorkflowDebugger.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/WorkflowDebugger.tsx @@ -1,3 +1,4 @@ +import { useEffect } from "react"; import { useParams } from "react-router-dom"; import { ReactFlowProvider } from "@xyflow/react"; @@ -6,6 +7,7 @@ import { WorkflowSettings } from "../types/workflowTypes"; import { getElements } from "./workflowEditorUtils"; import { getInitialParameters } from "./utils"; import { Workspace } from "./Workspace"; +import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore"; function WorkflowDebugger() { const { workflowPermanentId } = useParams(); @@ -13,6 +15,17 @@ function WorkflowDebugger() { workflowPermanentId, }); + const setParameters = useWorkflowParametersStore( + (state) => state.setParameters, + ); + + useEffect(() => { + if (workflow) { + const initialParameters = getInitialParameters(workflow); + setParameters(initialParameters); + } + }, [workflow, setParameters]); + if (!workflow) { return null; } @@ -42,7 +55,6 @@ function WorkflowDebugger() { state.setParameters, + ); + + useEffect(() => { + if (workflow) { + const initialParameters = getInitialParameters(workflow); + setParameters(initialParameters); + } + }, [workflow, setParameters]); + if (isLoading || isGlobalWorkflowsLoading) { return (
@@ -60,7 +73,6 @@ function WorkflowEditor() { >, -]; - -const WorkflowParametersStateContext = createContext< - WorkflowParametersState | undefined ->(undefined); - -export { WorkflowParametersStateContext }; diff --git a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx index 99209cd5..0a8ea2a4 100644 --- a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx @@ -44,7 +44,6 @@ import { FlowRenderer, type FlowRendererProps } from "./FlowRenderer"; import { AppNode, isWorkflowBlockNode, WorkflowBlockNode } from "./nodes"; import { WorkflowNodeLibraryPanel } from "./panels/WorkflowNodeLibraryPanel"; import { WorkflowParametersPanel } from "./panels/WorkflowParametersPanel"; -import { ParametersState } from "./types"; import { getWorkflowErrors } from "./workflowEditorUtils"; import { WorkflowHeader } from "./WorkflowHeader"; import { @@ -55,16 +54,12 @@ import { layout, startNode, } from "./workflowEditorUtils"; -import { WorkflowParametersStateContext } from "./WorkflowParametersStateContext"; const Constants = { NewBrowserCooldown: 30000, } as const; -type Props = Pick< - FlowRendererProps, - "initialTitle" | "initialParameters" | "workflow" -> & { +type Props = Pick & { initialNodes: Array; initialEdges: Array; showBrowser?: boolean; @@ -82,15 +77,12 @@ function Workspace({ initialNodes, initialEdges, initialTitle, - initialParameters, showBrowser = false, workflow, }: Props) { const { blockLabel, workflowPermanentId } = useParams(); const { workflowPanelState, setWorkflowPanelState, closeWorkflowPanel } = useWorkflowPanelStore(); - const [parameters, setParameters] = - useState(initialParameters); const debugStore = useDebugStore(); const [debuggableBlockCount, setDebuggableBlockCount] = useState(0); const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); @@ -335,208 +327,204 @@ function Workspace({ } return ( - -
- { - if (!open && cycleBrowser.isPending) { - return; - } - setOpenDialogue(open); - }} - > - - - Cycle (Get a new browser) - -
- {cycleBrowser.isPending ? ( - <> - Cooking you up a fresh browser... - - - ) : ( - "Abandon this browser for a new one. Are you sure?" - )} -
-
-
- - {!cycleBrowser.isPending && ( - - - - )} - - -
-
+
+ + + + {!cycleBrowser.isPending && ( + + + + )} + + + + - {/* header panel */} -
{ - promote("header"); - }} - > - { + promote("header"); + }} + > + { + if ( workflowPanelState.active && workflowPanelState.content === "parameters" - } - onParametersClick={() => { - if ( - workflowPanelState.active && - workflowPanelState.content === "parameters" - ) { - closeWorkflowPanel(); - promote("header"); - } else { - setWorkflowPanelState({ - active: true, - content: "parameters", - }); - promote("dropdown"); - } - }} - onSave={async () => { - const errors = getWorkflowErrors(nodes); - if (errors.length > 0) { - toast({ - title: "Can not save workflow because of errors:", - description: ( -
- {errors.map((error) => ( -

{error}

- ))} -
- ), - variant: "destructive", - }); - return; - } - await saveWorkflow.mutateAsync(); - }} - onRun={() => { + ) { closeWorkflowPanel(); promote("header"); - }} - /> -
- - {/* sub panels */} - {workflowPanelState.active && ( -
{ + } else { + setWorkflowPanelState({ + active: true, + content: "parameters", + }); promote("dropdown"); - }} - > - {workflowPanelState.content === "parameters" && ( - { - promote("dropdown"); - }} - /> - )} - {workflowPanelState.content === "nodeLibrary" && ( - { - promote("dropdown"); - }} - onNodeClick={(props) => { - addNode(props); - }} - /> - )} -
- )} + } + }} + onSave={async () => { + const errors = getWorkflowErrors(nodes); + if (errors.length > 0) { + toast({ + title: "Can not save workflow because of errors:", + description: ( +
+ {errors.map((error) => ( +

{error}

+ ))} +
+ ), + variant: "destructive", + }); + return; + } + await saveWorkflow.mutateAsync(); + }} + onRun={() => { + closeWorkflowPanel(); + promote("header"); + }} + /> +
- {debugStore.isDebugMode && ( -
{ - closeWorkflowPanel(); - promote("history"); - }} - > -
-
- -
+ {/* sub panels */} + {workflowPanelState.active && ( +
{ + promote("dropdown"); + }} + > + {workflowPanelState.content === "parameters" && ( + { + promote("dropdown"); + }} + /> + )} + {workflowPanelState.content === "nodeLibrary" && ( + { + promote("dropdown"); + }} + onNodeClick={(props) => { + addNode(props); + }} + /> + )} +
+ )} + + {debugStore.isDebugMode && ( +
{ + closeWorkflowPanel(); + promote("history"); + }} + > +
+
+
- )} +
+ )} - {/* infinite canvas */} - setDebuggableBlockCount(c)} - onMouseDownCapture={() => promote("infiniteCanvas")} - zIndex={rankedItems.infiniteCanvas} - /> + {/* infinite canvas */} + setDebuggableBlockCount(c)} + onMouseDownCapture={() => promote("infiniteCanvas")} + zIndex={rankedItems.infiniteCanvas} + /> - {/* browser */} - {showBrowser && ( - promote("browserWindow")} - > - {activeDebugSession && - activeDebugSession.browser_session_id && - !cycleBrowser.isPending ? ( - - ) : ( -
- Connecting to your browser... - -
- )} -
- )} -
- + {/* browser */} + {showBrowser && ( + promote("browserWindow")} + > + {activeDebugSession && + activeDebugSession.browser_session_id && + !cycleBrowser.isPending ? ( + + ) : ( +
+ Connecting to your browser... + +
+ )} +
+ )} +
); } diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/LoginNode/LoginBlockCredentialSelector.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/LoginNode/LoginBlockCredentialSelector.tsx index 8fac5fb6..74c711a2 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/LoginNode/LoginBlockCredentialSelector.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/LoginNode/LoginBlockCredentialSelector.tsx @@ -9,7 +9,7 @@ import { Skeleton } from "@/components/ui/skeleton"; import { useCredentialsQuery } from "@/routes/workflows/hooks/useCredentialsQuery"; import CloudContext from "@/store/CloudContext"; import { useContext } from "react"; -import { useWorkflowParametersState } from "../../useWorkflowParametersState"; +import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore"; import { CredentialsModal } from "@/routes/credentials/CredentialsModal"; import { PlusIcon } from "@radix-ui/react-icons"; import { @@ -30,8 +30,10 @@ type Props = { function LoginBlockCredentialSelector({ nodeId, value, onChange }: Props) { const { setIsOpen, setType } = useCredentialModalState(); const nodes = useNodes(); - const [workflowParameters, setWorkflowParameters] = - useWorkflowParametersState(); + const { + parameters: workflowParameters, + setParameters: setWorkflowParameters, + } = useWorkflowParametersStore(); const credentialParameters = workflowParameters.filter( (parameter) => parameter.parameterType === "credential" || @@ -170,16 +172,14 @@ function LoginBlockCredentialSelector({ nodeId, value, onChange }: Props) { { onChange?.(id); - setWorkflowParameters((prev) => { - return [ - ...prev, - { - parameterType: "credential", - credentialId: id, - key: id, - }, - ]; - }); + setWorkflowParameters([ + ...workflowParameters, + { + parameterType: "credential", + credentialId: id, + key: id, + }, + ]); }} /> diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/TaskNode/ParametersMultiSelect.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/TaskNode/ParametersMultiSelect.tsx index aae5c523..7b0bcd13 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/TaskNode/ParametersMultiSelect.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/TaskNode/ParametersMultiSelect.tsx @@ -1,5 +1,5 @@ import { MultiSelect } from "@/components/ui/multi-select"; -import { useWorkflowParametersState } from "../../useWorkflowParametersState"; +import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore"; import { HelpTooltip } from "@/components/HelpTooltip"; import { helpTooltips } from "../../helpContent"; @@ -14,7 +14,7 @@ function ParametersMultiSelect({ parameters, onParametersChange, }: Props) { - const [workflowParameters] = useWorkflowParametersState(); + const { parameters: workflowParameters } = useWorkflowParametersStore(); const keys = workflowParameters .map((parameter) => parameter.key) .concat(availableOutputParameters); diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/TaskNode/TaskNodeParametersPanel.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/TaskNode/TaskNodeParametersPanel.tsx index f844345c..cc8e55a0 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/TaskNode/TaskNodeParametersPanel.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/TaskNode/TaskNodeParametersPanel.tsx @@ -1,5 +1,5 @@ import { MultiSelect } from "@/components/ui/multi-select"; -import { useWorkflowParametersState } from "../../useWorkflowParametersState"; +import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore"; import { HelpTooltip } from "@/components/HelpTooltip"; import { helpTooltips } from "../../helpContent"; @@ -14,7 +14,7 @@ function TaskNodeParametersPanel({ parameters, onParametersChange, }: Props) { - const [workflowParameters] = useWorkflowParametersState(); + const { parameters: workflowParameters } = useWorkflowParametersStore(); const keys = workflowParameters .map((parameter) => parameter.key) .concat(availableOutputParameters); diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/WorkflowBlockParameterSelect.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/WorkflowBlockParameterSelect.tsx index 81365a0f..201e7208 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/WorkflowBlockParameterSelect.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/WorkflowBlockParameterSelect.tsx @@ -1,5 +1,5 @@ import { useEdges, useNodes } from "@xyflow/react"; -import { useWorkflowParametersState } from "../useWorkflowParametersState"; +import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore"; import { AppNode } from "."; import { getAvailableOutputParameterKeys } from "../workflowEditorUtils"; import { PlusIcon } from "@radix-ui/react-icons"; @@ -15,7 +15,7 @@ type Props = { function WorkflowBlockParameterSelect({ nodeId, onAdd }: Props) { const [content, setContent] = useState("parameters"); - const [workflowParameters] = useWorkflowParametersState(); + const { parameters: workflowParameters } = useWorkflowParametersStore(); const nodes = useNodes(); const edges = useEdges(); const outputParameterKeys = getAvailableOutputParameterKeys( diff --git a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParametersPanel.tsx b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParametersPanel.tsx index 3b017b5a..4214356d 100644 --- a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParametersPanel.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowParametersPanel.tsx @@ -1,5 +1,4 @@ import { useState } from "react"; -import { useWorkflowParametersState } from "../useWorkflowParametersState"; import { WorkflowParameterAddPanel } from "./WorkflowParameterAddPanel"; import { ParametersState } from "../types"; import { WorkflowParameterEditPanel } from "./WorkflowParameterEditPanel"; @@ -26,6 +25,7 @@ import { } from "@/components/ui/dropdown-menu"; import { useReactFlow } from "@xyflow/react"; import { useWorkflowHasChangesStore } from "@/store/WorkflowHasChangesStore"; +import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore"; import { ScrollArea, ScrollAreaViewport } from "@/components/ui/scroll-area"; import { WorkflowEditorParameterType, @@ -44,8 +44,10 @@ function WorkflowParametersPanel({ onMouseDownCapture }: Props) { const setHasChanges = useWorkflowHasChangesStore( (state) => state.setHasChanges, ); - const [workflowParameters, setWorkflowParameters] = - useWorkflowParametersState(); + const { + parameters: workflowParameters, + setParameters: setWorkflowParameters, + } = useWorkflowParametersStore(); const [operationPanelState, setOperationPanelState] = useState<{ active: boolean; operation: "add" | "edit"; diff --git a/skyvern-frontend/src/routes/workflows/editor/useWorkflowParametersState.ts b/skyvern-frontend/src/routes/workflows/editor/useWorkflowParametersState.ts deleted file mode 100644 index 3ca98d5a..00000000 --- a/skyvern-frontend/src/routes/workflows/editor/useWorkflowParametersState.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useContext } from "react"; -import { WorkflowParametersStateContext } from "./WorkflowParametersStateContext"; - -function useWorkflowParametersState() { - const value = useContext(WorkflowParametersStateContext); - if (value === undefined) { - throw new Error( - "useWorkflowParametersState must be used within a WorkflowParametersStateProvider", - ); - } - return value; -} - -export { useWorkflowParametersState }; diff --git a/skyvern-frontend/src/routes/workflows/hooks/useLabelChangeHandler.ts b/skyvern-frontend/src/routes/workflows/hooks/useLabelChangeHandler.ts index d89252c4..4c3c3aac 100644 --- a/skyvern-frontend/src/routes/workflows/hooks/useLabelChangeHandler.ts +++ b/skyvern-frontend/src/routes/workflows/hooks/useLabelChangeHandler.ts @@ -6,7 +6,7 @@ import { getUpdatedParametersAfterLabelUpdateForSourceParameterKey, } from "../editor/workflowEditorUtils"; import { useState } from "react"; -import { useWorkflowParametersState } from "../editor/useWorkflowParametersState"; +import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore"; type Props = { id: string; @@ -17,8 +17,10 @@ function useNodeLabelChangeHandler({ id, initialValue }: Props) { const [label, setLabel] = useState(initialValue); const nodes = useNodes(); const { setNodes } = useReactFlow(); - const [workflowParameters, setWorkflowParameters] = - useWorkflowParametersState(); + const { + parameters: workflowParameters, + setParameters: setWorkflowParameters, + } = useWorkflowParametersStore(); function handleLabelChange(value: string) { const existingLabels = nodes diff --git a/skyvern-frontend/src/store/WorkflowParametersStore.ts b/skyvern-frontend/src/store/WorkflowParametersStore.ts new file mode 100644 index 00000000..cfbdf753 --- /dev/null +++ b/skyvern-frontend/src/store/WorkflowParametersStore.ts @@ -0,0 +1,16 @@ +import { create } from "zustand"; +import { ParametersState } from "@/routes/workflows/editor/types"; + +interface WorkflowParametersStore { + parameters: ParametersState; + setParameters: (parameters: ParametersState) => void; +} + +const useWorkflowParametersStore = create((set) => { + return { + parameters: [], + setParameters: (parameters: ParametersState) => set({ parameters }), + }; +}); + +export { useWorkflowParametersStore };