replace workflow parameter React Context with a zustand store; use everywhere (#3187)
This commit is contained in:
@@ -1,11 +1,10 @@
|
|||||||
import { PlusIcon } from "@radix-ui/react-icons";
|
import { PlusIcon } from "@radix-ui/react-icons";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
import { WorkflowBlockParameterSelect } from "@/routes/workflows/editor/nodes/WorkflowBlockParameterSelect";
|
import { WorkflowBlockParameterSelect } from "@/routes/workflows/editor/nodes/WorkflowBlockParameterSelect";
|
||||||
|
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Cross2Icon } from "@radix-ui/react-icons";
|
import { Cross2Icon } from "@radix-ui/react-icons";
|
||||||
import "./workflow-block-input-set.css";
|
import "./workflow-block-input-set.css";
|
||||||
import { useWorkflowParametersState } from "@/routes/workflows/editor/useWorkflowParametersState";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onChange: (parameterKeys: Set<string>) => void;
|
onChange: (parameterKeys: Set<string>) => void;
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
@@ -16,7 +15,7 @@ function WorkflowBlockInputSet(props: Props) {
|
|||||||
const { nodeId, onChange, values } = props;
|
const { nodeId, onChange, values } = props;
|
||||||
const [parameterKeys, setParameterKeys] = useState<Set<string>>(values);
|
const [parameterKeys, setParameterKeys] = useState<Set<string>>(values);
|
||||||
const hasKeys = parameterKeys.size > 0;
|
const hasKeys = parameterKeys.size > 0;
|
||||||
const [workflowParameters] = useWorkflowParametersState();
|
const { parameters: workflowParameters } = useWorkflowParametersStore();
|
||||||
const availableParameterKeys = new Set(
|
const availableParameterKeys = new Set(
|
||||||
workflowParameters.map((parameter) => parameter.key),
|
workflowParameters.map((parameter) => parameter.key),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { useCredentialsQuery } from "../hooks/useCredentialsQuery";
|
import { useCredentialsQuery } from "../hooks/useCredentialsQuery";
|
||||||
import { useWorkflowParametersState } from "../editor/useWorkflowParametersState";
|
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
|
||||||
import { WorkflowParameterValueType } from "../types/workflowTypes";
|
import { WorkflowParameterValueType } from "../types/workflowTypes";
|
||||||
import { PlusIcon } from "@radix-ui/react-icons";
|
import { PlusIcon } from "@radix-ui/react-icons";
|
||||||
import {
|
import {
|
||||||
@@ -24,7 +24,7 @@ type Props = {
|
|||||||
function CredentialParameterSourceSelector({ value, onChange }: Props) {
|
function CredentialParameterSourceSelector({ value, onChange }: Props) {
|
||||||
const { data: credentials, isFetching } = useCredentialsQuery();
|
const { data: credentials, isFetching } = useCredentialsQuery();
|
||||||
const { setIsOpen, setType } = useCredentialModalState();
|
const { setIsOpen, setType } = useCredentialModalState();
|
||||||
const [workflowParameters] = useWorkflowParametersState();
|
const { parameters: workflowParameters } = useWorkflowParametersStore();
|
||||||
const workflowParametersOfTypeCredentialId = workflowParameters.filter(
|
const workflowParametersOfTypeCredentialId = workflowParameters.filter(
|
||||||
(parameter) =>
|
(parameter) =>
|
||||||
parameter.parameterType === "workflow" &&
|
parameter.parameterType === "workflow" &&
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useNodes } from "@xyflow/react";
|
import { useNodes } from "@xyflow/react";
|
||||||
import { useWorkflowParametersState } from "../editor/useWorkflowParametersState";
|
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
|
||||||
import { AppNode, isWorkflowBlockNode } from "../editor/nodes";
|
import { AppNode, isWorkflowBlockNode } from "../editor/nodes";
|
||||||
import { getOutputParameterKey } from "../editor/workflowEditorUtils";
|
import { getOutputParameterKey } from "../editor/workflowEditorUtils";
|
||||||
import {
|
import {
|
||||||
@@ -16,7 +16,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function SourceParameterKeySelector({ value, onChange }: Props) {
|
function SourceParameterKeySelector({ value, onChange }: Props) {
|
||||||
const [workflowParameters] = useWorkflowParametersState();
|
const { parameters: workflowParameters } = useWorkflowParametersStore();
|
||||||
const nodes = useNodes<AppNode>();
|
const nodes = useNodes<AppNode>();
|
||||||
const contextParameterKeys = workflowParameters
|
const contextParameterKeys = workflowParameters
|
||||||
.filter((parameter) => parameter.parameterType !== "credential")
|
.filter((parameter) => parameter.parameterType !== "credential")
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
useWorkflowSave,
|
useWorkflowSave,
|
||||||
type WorkflowSaveData,
|
type WorkflowSaveData,
|
||||||
} from "@/store/WorkflowHasChangesStore";
|
} from "@/store/WorkflowHasChangesStore";
|
||||||
|
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
|
||||||
import { useWorkflowTitleStore } from "@/store/WorkflowTitleStore";
|
import { useWorkflowTitleStore } from "@/store/WorkflowTitleStore";
|
||||||
import { ReloadIcon } from "@radix-ui/react-icons";
|
import { ReloadIcon } from "@radix-ui/react-icons";
|
||||||
import {
|
import {
|
||||||
@@ -223,7 +224,7 @@ type Props = {
|
|||||||
onNodesChange: (changes: Array<NodeChange<AppNode>>) => void;
|
onNodesChange: (changes: Array<NodeChange<AppNode>>) => void;
|
||||||
onEdgesChange: (changes: Array<EdgeChange>) => void;
|
onEdgesChange: (changes: Array<EdgeChange>) => void;
|
||||||
initialTitle: string;
|
initialTitle: string;
|
||||||
initialParameters: ParametersState;
|
// initialParameters: ParametersState;
|
||||||
workflow: WorkflowApiResponse;
|
workflow: WorkflowApiResponse;
|
||||||
onDebuggableBlockCountChange: (count: number) => void;
|
onDebuggableBlockCountChange: (count: number) => void;
|
||||||
onMouseDownCapture?: () => void;
|
onMouseDownCapture?: () => void;
|
||||||
@@ -238,7 +239,7 @@ function FlowRenderer({
|
|||||||
onNodesChange,
|
onNodesChange,
|
||||||
onEdgesChange,
|
onEdgesChange,
|
||||||
initialTitle,
|
initialTitle,
|
||||||
initialParameters,
|
// initialParameters,
|
||||||
workflow,
|
workflow,
|
||||||
onDebuggableBlockCountChange,
|
onDebuggableBlockCountChange,
|
||||||
onMouseDownCapture,
|
onMouseDownCapture,
|
||||||
@@ -247,7 +248,8 @@ function FlowRenderer({
|
|||||||
const reactFlowInstance = useReactFlow();
|
const reactFlowInstance = useReactFlow();
|
||||||
const debugStore = useDebugStore();
|
const debugStore = useDebugStore();
|
||||||
const { title, initializeTitle } = useWorkflowTitleStore();
|
const { title, initializeTitle } = useWorkflowTitleStore();
|
||||||
const [parameters] = useState<ParametersState>(initialParameters);
|
// const [parameters] = useState<ParametersState>(initialParameters);
|
||||||
|
const parameters = useWorkflowParametersStore((state) => state.parameters);
|
||||||
const nodesInitialized = useNodesInitialized();
|
const nodesInitialized = useNodesInitialized();
|
||||||
const [shouldConstrainPan, setShouldConstrainPan] = useState(false);
|
const [shouldConstrainPan, setShouldConstrainPan] = useState(false);
|
||||||
const onNodesChangeTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const onNodesChangeTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { ReactFlowProvider } from "@xyflow/react";
|
import { ReactFlowProvider } from "@xyflow/react";
|
||||||
|
|
||||||
@@ -6,6 +7,7 @@ import { WorkflowSettings } from "../types/workflowTypes";
|
|||||||
import { getElements } from "./workflowEditorUtils";
|
import { getElements } from "./workflowEditorUtils";
|
||||||
import { getInitialParameters } from "./utils";
|
import { getInitialParameters } from "./utils";
|
||||||
import { Workspace } from "./Workspace";
|
import { Workspace } from "./Workspace";
|
||||||
|
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
|
||||||
|
|
||||||
function WorkflowDebugger() {
|
function WorkflowDebugger() {
|
||||||
const { workflowPermanentId } = useParams();
|
const { workflowPermanentId } = useParams();
|
||||||
@@ -13,6 +15,17 @@ function WorkflowDebugger() {
|
|||||||
workflowPermanentId,
|
workflowPermanentId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const setParameters = useWorkflowParametersStore(
|
||||||
|
(state) => state.setParameters,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (workflow) {
|
||||||
|
const initialParameters = getInitialParameters(workflow);
|
||||||
|
setParameters(initialParameters);
|
||||||
|
}
|
||||||
|
}, [workflow, setParameters]);
|
||||||
|
|
||||||
if (!workflow) {
|
if (!workflow) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -42,7 +55,6 @@ function WorkflowDebugger() {
|
|||||||
<Workspace
|
<Workspace
|
||||||
initialEdges={elements.edges}
|
initialEdges={elements.edges}
|
||||||
initialNodes={elements.nodes}
|
initialNodes={elements.nodes}
|
||||||
initialParameters={getInitialParameters(workflow)}
|
|
||||||
initialTitle={workflow.title}
|
initialTitle={workflow.title}
|
||||||
showBrowser={true}
|
showBrowser={true}
|
||||||
workflow={workflow}
|
workflow={workflow}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { ReactFlowProvider } from "@xyflow/react";
|
import { ReactFlowProvider } from "@xyflow/react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
import { useEffect } from "react";
|
||||||
import { useWorkflowQuery } from "../hooks/useWorkflowQuery";
|
import { useWorkflowQuery } from "../hooks/useWorkflowQuery";
|
||||||
import { getElements } from "./workflowEditorUtils";
|
import { getElements } from "./workflowEditorUtils";
|
||||||
import { LogoMinimized } from "@/components/LogoMinimized";
|
import { LogoMinimized } from "@/components/LogoMinimized";
|
||||||
import { WorkflowSettings } from "../types/workflowTypes";
|
import { WorkflowSettings } from "../types/workflowTypes";
|
||||||
import { useGlobalWorkflowsQuery } from "../hooks/useGlobalWorkflowsQuery";
|
import { useGlobalWorkflowsQuery } from "../hooks/useGlobalWorkflowsQuery";
|
||||||
|
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
|
||||||
import { getInitialParameters } from "./utils";
|
import { getInitialParameters } from "./utils";
|
||||||
import { Workspace } from "./Workspace";
|
import { Workspace } from "./Workspace";
|
||||||
|
|
||||||
@@ -18,6 +20,17 @@ function WorkflowEditor() {
|
|||||||
const { data: globalWorkflows, isLoading: isGlobalWorkflowsLoading } =
|
const { data: globalWorkflows, isLoading: isGlobalWorkflowsLoading } =
|
||||||
useGlobalWorkflowsQuery();
|
useGlobalWorkflowsQuery();
|
||||||
|
|
||||||
|
const setParameters = useWorkflowParametersStore(
|
||||||
|
(state) => state.setParameters,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (workflow) {
|
||||||
|
const initialParameters = getInitialParameters(workflow);
|
||||||
|
setParameters(initialParameters);
|
||||||
|
}
|
||||||
|
}, [workflow, setParameters]);
|
||||||
|
|
||||||
if (isLoading || isGlobalWorkflowsLoading) {
|
if (isLoading || isGlobalWorkflowsLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen w-full items-center justify-center">
|
<div className="flex h-screen w-full items-center justify-center">
|
||||||
@@ -60,7 +73,6 @@ function WorkflowEditor() {
|
|||||||
<Workspace
|
<Workspace
|
||||||
initialEdges={elements.edges}
|
initialEdges={elements.edges}
|
||||||
initialNodes={elements.nodes}
|
initialNodes={elements.nodes}
|
||||||
initialParameters={getInitialParameters(workflow)}
|
|
||||||
initialTitle={workflow.title}
|
initialTitle={workflow.title}
|
||||||
showBrowser={false}
|
showBrowser={false}
|
||||||
workflow={workflow}
|
workflow={workflow}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { createContext } from "react";
|
|
||||||
import { ParametersState } from "./types";
|
|
||||||
|
|
||||||
type WorkflowParametersState = [
|
|
||||||
ParametersState,
|
|
||||||
React.Dispatch<React.SetStateAction<ParametersState>>,
|
|
||||||
];
|
|
||||||
|
|
||||||
const WorkflowParametersStateContext = createContext<
|
|
||||||
WorkflowParametersState | undefined
|
|
||||||
>(undefined);
|
|
||||||
|
|
||||||
export { WorkflowParametersStateContext };
|
|
||||||
@@ -44,7 +44,6 @@ import { FlowRenderer, type FlowRendererProps } from "./FlowRenderer";
|
|||||||
import { AppNode, isWorkflowBlockNode, WorkflowBlockNode } from "./nodes";
|
import { AppNode, isWorkflowBlockNode, WorkflowBlockNode } from "./nodes";
|
||||||
import { WorkflowNodeLibraryPanel } from "./panels/WorkflowNodeLibraryPanel";
|
import { WorkflowNodeLibraryPanel } from "./panels/WorkflowNodeLibraryPanel";
|
||||||
import { WorkflowParametersPanel } from "./panels/WorkflowParametersPanel";
|
import { WorkflowParametersPanel } from "./panels/WorkflowParametersPanel";
|
||||||
import { ParametersState } from "./types";
|
|
||||||
import { getWorkflowErrors } from "./workflowEditorUtils";
|
import { getWorkflowErrors } from "./workflowEditorUtils";
|
||||||
import { WorkflowHeader } from "./WorkflowHeader";
|
import { WorkflowHeader } from "./WorkflowHeader";
|
||||||
import {
|
import {
|
||||||
@@ -55,16 +54,12 @@ import {
|
|||||||
layout,
|
layout,
|
||||||
startNode,
|
startNode,
|
||||||
} from "./workflowEditorUtils";
|
} from "./workflowEditorUtils";
|
||||||
import { WorkflowParametersStateContext } from "./WorkflowParametersStateContext";
|
|
||||||
|
|
||||||
const Constants = {
|
const Constants = {
|
||||||
NewBrowserCooldown: 30000,
|
NewBrowserCooldown: 30000,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
type Props = Pick<
|
type Props = Pick<FlowRendererProps, "initialTitle" | "workflow"> & {
|
||||||
FlowRendererProps,
|
|
||||||
"initialTitle" | "initialParameters" | "workflow"
|
|
||||||
> & {
|
|
||||||
initialNodes: Array<AppNode>;
|
initialNodes: Array<AppNode>;
|
||||||
initialEdges: Array<Edge>;
|
initialEdges: Array<Edge>;
|
||||||
showBrowser?: boolean;
|
showBrowser?: boolean;
|
||||||
@@ -82,15 +77,12 @@ function Workspace({
|
|||||||
initialNodes,
|
initialNodes,
|
||||||
initialEdges,
|
initialEdges,
|
||||||
initialTitle,
|
initialTitle,
|
||||||
initialParameters,
|
|
||||||
showBrowser = false,
|
showBrowser = false,
|
||||||
workflow,
|
workflow,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { blockLabel, workflowPermanentId } = useParams();
|
const { blockLabel, workflowPermanentId } = useParams();
|
||||||
const { workflowPanelState, setWorkflowPanelState, closeWorkflowPanel } =
|
const { workflowPanelState, setWorkflowPanelState, closeWorkflowPanel } =
|
||||||
useWorkflowPanelStore();
|
useWorkflowPanelStore();
|
||||||
const [parameters, setParameters] =
|
|
||||||
useState<ParametersState>(initialParameters);
|
|
||||||
const debugStore = useDebugStore();
|
const debugStore = useDebugStore();
|
||||||
const [debuggableBlockCount, setDebuggableBlockCount] = useState(0);
|
const [debuggableBlockCount, setDebuggableBlockCount] = useState(0);
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
@@ -335,208 +327,204 @@ function Workspace({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkflowParametersStateContext.Provider
|
<div className="relative h-full w-full">
|
||||||
value={[parameters, setParameters]}
|
<Dialog
|
||||||
>
|
open={openDialogue}
|
||||||
<div className="relative h-full w-full">
|
onOpenChange={(open) => {
|
||||||
<Dialog
|
if (!open && cycleBrowser.isPending) {
|
||||||
open={openDialogue}
|
return;
|
||||||
onOpenChange={(open) => {
|
}
|
||||||
if (!open && cycleBrowser.isPending) {
|
setOpenDialogue(open);
|
||||||
return;
|
}}
|
||||||
}
|
>
|
||||||
setOpenDialogue(open);
|
<DialogContent>
|
||||||
}}
|
<DialogHeader>
|
||||||
>
|
<DialogTitle>Cycle (Get a new browser)</DialogTitle>
|
||||||
<DialogContent>
|
<DialogDescription>
|
||||||
<DialogHeader>
|
<div className="pb-2 pt-4 text-sm text-slate-400">
|
||||||
<DialogTitle>Cycle (Get a new browser)</DialogTitle>
|
{cycleBrowser.isPending ? (
|
||||||
<DialogDescription>
|
<>
|
||||||
<div className="pb-2 pt-4 text-sm text-slate-400">
|
Cooking you up a fresh browser...
|
||||||
{cycleBrowser.isPending ? (
|
<AnimatedWave text=".‧₊˚ ⋅ ✨★ ‧₊˚ ⋅" />
|
||||||
<>
|
</>
|
||||||
Cooking you up a fresh browser...
|
) : (
|
||||||
<AnimatedWave text=".‧₊˚ ⋅ ✨★ ‧₊˚ ⋅" />
|
"Abandon this browser for a new one. Are you sure?"
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
"Abandon this browser for a new one. Are you sure?"
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<DialogFooter>
|
|
||||||
{!cycleBrowser.isPending && (
|
|
||||||
<DialogClose asChild>
|
|
||||||
<Button variant="secondary">Cancel</Button>
|
|
||||||
</DialogClose>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
onClick={() => {
|
|
||||||
cycleBrowser.mutate(workflowPermanentId!);
|
|
||||||
}}
|
|
||||||
disabled={cycleBrowser.isPending}
|
|
||||||
>
|
|
||||||
Yes, Continue{" "}
|
|
||||||
{cycleBrowser.isPending && (
|
|
||||||
<ReloadIcon className="ml-2 size-4 animate-spin" />
|
|
||||||
)}
|
)}
|
||||||
</Button>
|
</div>
|
||||||
</DialogFooter>
|
</DialogDescription>
|
||||||
</DialogContent>
|
</DialogHeader>
|
||||||
</Dialog>
|
<DialogFooter>
|
||||||
|
{!cycleBrowser.isPending && (
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button variant="secondary">Cancel</Button>
|
||||||
|
</DialogClose>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
onClick={() => {
|
||||||
|
cycleBrowser.mutate(workflowPermanentId!);
|
||||||
|
}}
|
||||||
|
disabled={cycleBrowser.isPending}
|
||||||
|
>
|
||||||
|
Yes, Continue{" "}
|
||||||
|
{cycleBrowser.isPending && (
|
||||||
|
<ReloadIcon className="ml-2 size-4 animate-spin" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
{/* header panel */}
|
{/* header panel */}
|
||||||
<div
|
<div
|
||||||
className="absolute left-6 right-6 top-8 h-20"
|
className="absolute left-6 right-6 top-8 h-20"
|
||||||
style={{ zIndex: rankedItems.header ?? 3 }}
|
style={{ zIndex: rankedItems.header ?? 3 }}
|
||||||
onMouseDownCapture={() => {
|
onMouseDownCapture={() => {
|
||||||
promote("header");
|
promote("header");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<WorkflowHeader
|
<WorkflowHeader
|
||||||
debuggableBlockCount={debuggableBlockCount}
|
debuggableBlockCount={debuggableBlockCount}
|
||||||
saving={workflowChangesStore.saveIsPending}
|
saving={workflowChangesStore.saveIsPending}
|
||||||
parametersPanelOpen={
|
parametersPanelOpen={
|
||||||
|
workflowPanelState.active &&
|
||||||
|
workflowPanelState.content === "parameters"
|
||||||
|
}
|
||||||
|
onParametersClick={() => {
|
||||||
|
if (
|
||||||
workflowPanelState.active &&
|
workflowPanelState.active &&
|
||||||
workflowPanelState.content === "parameters"
|
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: (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{errors.map((error) => (
|
|
||||||
<p key={error}>{error}</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await saveWorkflow.mutateAsync();
|
|
||||||
}}
|
|
||||||
onRun={() => {
|
|
||||||
closeWorkflowPanel();
|
closeWorkflowPanel();
|
||||||
promote("header");
|
promote("header");
|
||||||
}}
|
} else {
|
||||||
/>
|
setWorkflowPanelState({
|
||||||
</div>
|
active: true,
|
||||||
|
content: "parameters",
|
||||||
{/* sub panels */}
|
});
|
||||||
{workflowPanelState.active && (
|
|
||||||
<div
|
|
||||||
className="absolute right-6 top-[7.75rem]"
|
|
||||||
style={{ zIndex: rankedItems.dropdown ?? 2 }}
|
|
||||||
onMouseDownCapture={() => {
|
|
||||||
promote("dropdown");
|
promote("dropdown");
|
||||||
}}
|
}
|
||||||
>
|
}}
|
||||||
{workflowPanelState.content === "parameters" && (
|
onSave={async () => {
|
||||||
<WorkflowParametersPanel
|
const errors = getWorkflowErrors(nodes);
|
||||||
onMouseDownCapture={() => {
|
if (errors.length > 0) {
|
||||||
promote("dropdown");
|
toast({
|
||||||
}}
|
title: "Can not save workflow because of errors:",
|
||||||
/>
|
description: (
|
||||||
)}
|
<div className="space-y-2">
|
||||||
{workflowPanelState.content === "nodeLibrary" && (
|
{errors.map((error) => (
|
||||||
<WorkflowNodeLibraryPanel
|
<p key={error}>{error}</p>
|
||||||
onMouseDownCapture={() => {
|
))}
|
||||||
promote("dropdown");
|
</div>
|
||||||
}}
|
),
|
||||||
onNodeClick={(props) => {
|
variant: "destructive",
|
||||||
addNode(props);
|
});
|
||||||
}}
|
return;
|
||||||
/>
|
}
|
||||||
)}
|
await saveWorkflow.mutateAsync();
|
||||||
</div>
|
}}
|
||||||
)}
|
onRun={() => {
|
||||||
|
closeWorkflowPanel();
|
||||||
|
promote("header");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{debugStore.isDebugMode && (
|
{/* sub panels */}
|
||||||
<div
|
{workflowPanelState.active && (
|
||||||
className="absolute right-6 top-[8.5rem] h-[calc(100vh-9.5rem)]"
|
<div
|
||||||
style={{ zIndex: rankedItems.history ?? 1 }}
|
className="absolute right-6 top-[7.75rem]"
|
||||||
onMouseDownCapture={() => {
|
style={{ zIndex: rankedItems.dropdown ?? 2 }}
|
||||||
closeWorkflowPanel();
|
onMouseDownCapture={() => {
|
||||||
promote("history");
|
promote("dropdown");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="pointer-events-none absolute right-0 top-0 flex h-full w-[400px] flex-col items-end justify-end">
|
{workflowPanelState.content === "parameters" && (
|
||||||
<div className="pointer-events-auto relative h-full w-full overflow-hidden rounded-xl border-2 border-slate-500">
|
<WorkflowParametersPanel
|
||||||
<WorkflowDebuggerRun />
|
onMouseDownCapture={() => {
|
||||||
</div>
|
promote("dropdown");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{workflowPanelState.content === "nodeLibrary" && (
|
||||||
|
<WorkflowNodeLibraryPanel
|
||||||
|
onMouseDownCapture={() => {
|
||||||
|
promote("dropdown");
|
||||||
|
}}
|
||||||
|
onNodeClick={(props) => {
|
||||||
|
addNode(props);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{debugStore.isDebugMode && (
|
||||||
|
<div
|
||||||
|
className="absolute right-6 top-[8.5rem] h-[calc(100vh-9.5rem)]"
|
||||||
|
style={{ zIndex: rankedItems.history ?? 1 }}
|
||||||
|
onMouseDownCapture={() => {
|
||||||
|
closeWorkflowPanel();
|
||||||
|
promote("history");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="pointer-events-none absolute right-0 top-0 flex h-full w-[400px] flex-col items-end justify-end">
|
||||||
|
<div className="pointer-events-auto relative h-full w-full overflow-hidden rounded-xl border-2 border-slate-500">
|
||||||
|
<WorkflowDebuggerRun />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* infinite canvas */}
|
{/* infinite canvas */}
|
||||||
<FlowRenderer
|
<FlowRenderer
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
setNodes={setNodes}
|
setNodes={setNodes}
|
||||||
setEdges={setEdges}
|
setEdges={setEdges}
|
||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
initialTitle={initialTitle}
|
initialTitle={initialTitle}
|
||||||
initialParameters={initialParameters}
|
// initialParameters={initialParameters}
|
||||||
workflow={workflow}
|
workflow={workflow}
|
||||||
onDebuggableBlockCountChange={(c) => setDebuggableBlockCount(c)}
|
onDebuggableBlockCountChange={(c) => setDebuggableBlockCount(c)}
|
||||||
onMouseDownCapture={() => promote("infiniteCanvas")}
|
onMouseDownCapture={() => promote("infiniteCanvas")}
|
||||||
zIndex={rankedItems.infiniteCanvas}
|
zIndex={rankedItems.infiniteCanvas}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* browser */}
|
{/* browser */}
|
||||||
{showBrowser && (
|
{showBrowser && (
|
||||||
<FloatingWindow
|
<FloatingWindow
|
||||||
title={browserTitle}
|
title={browserTitle}
|
||||||
bounded={false}
|
bounded={false}
|
||||||
initialPosition={initialBrowserPosition}
|
initialPosition={initialBrowserPosition}
|
||||||
initialWidth={initialWidth}
|
initialWidth={initialWidth}
|
||||||
initialHeight={initialHeight}
|
initialHeight={initialHeight}
|
||||||
showMaximizeButton={true}
|
showMaximizeButton={true}
|
||||||
showMinimizeButton={true}
|
showMinimizeButton={true}
|
||||||
showPowerButton={blockLabel === undefined && showPowerButton}
|
showPowerButton={blockLabel === undefined && showPowerButton}
|
||||||
showReloadButton={true}
|
showReloadButton={true}
|
||||||
zIndex={rankedItems.browserWindow ?? 4}
|
zIndex={rankedItems.browserWindow ?? 4}
|
||||||
// --
|
// --
|
||||||
onCycle={handleOnCycle}
|
onCycle={handleOnCycle}
|
||||||
onFocus={() => promote("browserWindow")}
|
onFocus={() => promote("browserWindow")}
|
||||||
>
|
>
|
||||||
{activeDebugSession &&
|
{activeDebugSession &&
|
||||||
activeDebugSession.browser_session_id &&
|
activeDebugSession.browser_session_id &&
|
||||||
!cycleBrowser.isPending ? (
|
!cycleBrowser.isPending ? (
|
||||||
<BrowserStream
|
<BrowserStream
|
||||||
interactive={interactor === "human"}
|
interactive={interactor === "human"}
|
||||||
browserSessionId={activeDebugSession.browser_session_id}
|
browserSessionId={activeDebugSession.browser_session_id}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center gap-2 pb-2 pt-4 text-sm text-slate-400">
|
<div className="flex h-full w-full flex-col items-center justify-center gap-2 pb-2 pt-4 text-sm text-slate-400">
|
||||||
Connecting to your browser...
|
Connecting to your browser...
|
||||||
<AnimatedWave text=".‧₊˚ ⋅ ✨★ ‧₊˚ ⋅" />
|
<AnimatedWave text=".‧₊˚ ⋅ ✨★ ‧₊˚ ⋅" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</FloatingWindow>
|
</FloatingWindow>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</WorkflowParametersStateContext.Provider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Skeleton } from "@/components/ui/skeleton";
|
|||||||
import { useCredentialsQuery } from "@/routes/workflows/hooks/useCredentialsQuery";
|
import { useCredentialsQuery } from "@/routes/workflows/hooks/useCredentialsQuery";
|
||||||
import CloudContext from "@/store/CloudContext";
|
import CloudContext from "@/store/CloudContext";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { useWorkflowParametersState } from "../../useWorkflowParametersState";
|
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
|
||||||
import { CredentialsModal } from "@/routes/credentials/CredentialsModal";
|
import { CredentialsModal } from "@/routes/credentials/CredentialsModal";
|
||||||
import { PlusIcon } from "@radix-ui/react-icons";
|
import { PlusIcon } from "@radix-ui/react-icons";
|
||||||
import {
|
import {
|
||||||
@@ -30,8 +30,10 @@ type Props = {
|
|||||||
function LoginBlockCredentialSelector({ nodeId, value, onChange }: Props) {
|
function LoginBlockCredentialSelector({ nodeId, value, onChange }: Props) {
|
||||||
const { setIsOpen, setType } = useCredentialModalState();
|
const { setIsOpen, setType } = useCredentialModalState();
|
||||||
const nodes = useNodes<AppNode>();
|
const nodes = useNodes<AppNode>();
|
||||||
const [workflowParameters, setWorkflowParameters] =
|
const {
|
||||||
useWorkflowParametersState();
|
parameters: workflowParameters,
|
||||||
|
setParameters: setWorkflowParameters,
|
||||||
|
} = useWorkflowParametersStore();
|
||||||
const credentialParameters = workflowParameters.filter(
|
const credentialParameters = workflowParameters.filter(
|
||||||
(parameter) =>
|
(parameter) =>
|
||||||
parameter.parameterType === "credential" ||
|
parameter.parameterType === "credential" ||
|
||||||
@@ -170,16 +172,14 @@ function LoginBlockCredentialSelector({ nodeId, value, onChange }: Props) {
|
|||||||
<CredentialsModal
|
<CredentialsModal
|
||||||
onCredentialCreated={(id) => {
|
onCredentialCreated={(id) => {
|
||||||
onChange?.(id);
|
onChange?.(id);
|
||||||
setWorkflowParameters((prev) => {
|
setWorkflowParameters([
|
||||||
return [
|
...workflowParameters,
|
||||||
...prev,
|
{
|
||||||
{
|
parameterType: "credential",
|
||||||
parameterType: "credential",
|
credentialId: id,
|
||||||
credentialId: id,
|
key: id,
|
||||||
key: id,
|
},
|
||||||
},
|
]);
|
||||||
];
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { MultiSelect } from "@/components/ui/multi-select";
|
import { MultiSelect } from "@/components/ui/multi-select";
|
||||||
import { useWorkflowParametersState } from "../../useWorkflowParametersState";
|
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
|
||||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||||
import { helpTooltips } from "../../helpContent";
|
import { helpTooltips } from "../../helpContent";
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ function ParametersMultiSelect({
|
|||||||
parameters,
|
parameters,
|
||||||
onParametersChange,
|
onParametersChange,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [workflowParameters] = useWorkflowParametersState();
|
const { parameters: workflowParameters } = useWorkflowParametersStore();
|
||||||
const keys = workflowParameters
|
const keys = workflowParameters
|
||||||
.map((parameter) => parameter.key)
|
.map((parameter) => parameter.key)
|
||||||
.concat(availableOutputParameters);
|
.concat(availableOutputParameters);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { MultiSelect } from "@/components/ui/multi-select";
|
import { MultiSelect } from "@/components/ui/multi-select";
|
||||||
import { useWorkflowParametersState } from "../../useWorkflowParametersState";
|
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
|
||||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||||
import { helpTooltips } from "../../helpContent";
|
import { helpTooltips } from "../../helpContent";
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ function TaskNodeParametersPanel({
|
|||||||
parameters,
|
parameters,
|
||||||
onParametersChange,
|
onParametersChange,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [workflowParameters] = useWorkflowParametersState();
|
const { parameters: workflowParameters } = useWorkflowParametersStore();
|
||||||
const keys = workflowParameters
|
const keys = workflowParameters
|
||||||
.map((parameter) => parameter.key)
|
.map((parameter) => parameter.key)
|
||||||
.concat(availableOutputParameters);
|
.concat(availableOutputParameters);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEdges, useNodes } from "@xyflow/react";
|
import { useEdges, useNodes } from "@xyflow/react";
|
||||||
import { useWorkflowParametersState } from "../useWorkflowParametersState";
|
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
|
||||||
import { AppNode } from ".";
|
import { AppNode } from ".";
|
||||||
import { getAvailableOutputParameterKeys } from "../workflowEditorUtils";
|
import { getAvailableOutputParameterKeys } from "../workflowEditorUtils";
|
||||||
import { PlusIcon } from "@radix-ui/react-icons";
|
import { PlusIcon } from "@radix-ui/react-icons";
|
||||||
@@ -15,7 +15,7 @@ type Props = {
|
|||||||
|
|
||||||
function WorkflowBlockParameterSelect({ nodeId, onAdd }: Props) {
|
function WorkflowBlockParameterSelect({ nodeId, onAdd }: Props) {
|
||||||
const [content, setContent] = useState("parameters");
|
const [content, setContent] = useState("parameters");
|
||||||
const [workflowParameters] = useWorkflowParametersState();
|
const { parameters: workflowParameters } = useWorkflowParametersStore();
|
||||||
const nodes = useNodes<AppNode>();
|
const nodes = useNodes<AppNode>();
|
||||||
const edges = useEdges();
|
const edges = useEdges();
|
||||||
const outputParameterKeys = getAvailableOutputParameterKeys(
|
const outputParameterKeys = getAvailableOutputParameterKeys(
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useWorkflowParametersState } from "../useWorkflowParametersState";
|
|
||||||
import { WorkflowParameterAddPanel } from "./WorkflowParameterAddPanel";
|
import { WorkflowParameterAddPanel } from "./WorkflowParameterAddPanel";
|
||||||
import { ParametersState } from "../types";
|
import { ParametersState } from "../types";
|
||||||
import { WorkflowParameterEditPanel } from "./WorkflowParameterEditPanel";
|
import { WorkflowParameterEditPanel } from "./WorkflowParameterEditPanel";
|
||||||
@@ -26,6 +25,7 @@ import {
|
|||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { useReactFlow } from "@xyflow/react";
|
import { useReactFlow } from "@xyflow/react";
|
||||||
import { useWorkflowHasChangesStore } from "@/store/WorkflowHasChangesStore";
|
import { useWorkflowHasChangesStore } from "@/store/WorkflowHasChangesStore";
|
||||||
|
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
|
||||||
import { ScrollArea, ScrollAreaViewport } from "@/components/ui/scroll-area";
|
import { ScrollArea, ScrollAreaViewport } from "@/components/ui/scroll-area";
|
||||||
import {
|
import {
|
||||||
WorkflowEditorParameterType,
|
WorkflowEditorParameterType,
|
||||||
@@ -44,8 +44,10 @@ function WorkflowParametersPanel({ onMouseDownCapture }: Props) {
|
|||||||
const setHasChanges = useWorkflowHasChangesStore(
|
const setHasChanges = useWorkflowHasChangesStore(
|
||||||
(state) => state.setHasChanges,
|
(state) => state.setHasChanges,
|
||||||
);
|
);
|
||||||
const [workflowParameters, setWorkflowParameters] =
|
const {
|
||||||
useWorkflowParametersState();
|
parameters: workflowParameters,
|
||||||
|
setParameters: setWorkflowParameters,
|
||||||
|
} = useWorkflowParametersStore();
|
||||||
const [operationPanelState, setOperationPanelState] = useState<{
|
const [operationPanelState, setOperationPanelState] = useState<{
|
||||||
active: boolean;
|
active: boolean;
|
||||||
operation: "add" | "edit";
|
operation: "add" | "edit";
|
||||||
|
|||||||
@@ -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 };
|
|
||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
getUpdatedParametersAfterLabelUpdateForSourceParameterKey,
|
getUpdatedParametersAfterLabelUpdateForSourceParameterKey,
|
||||||
} from "../editor/workflowEditorUtils";
|
} from "../editor/workflowEditorUtils";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useWorkflowParametersState } from "../editor/useWorkflowParametersState";
|
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -17,8 +17,10 @@ function useNodeLabelChangeHandler({ id, initialValue }: Props) {
|
|||||||
const [label, setLabel] = useState(initialValue);
|
const [label, setLabel] = useState(initialValue);
|
||||||
const nodes = useNodes<AppNode>();
|
const nodes = useNodes<AppNode>();
|
||||||
const { setNodes } = useReactFlow();
|
const { setNodes } = useReactFlow();
|
||||||
const [workflowParameters, setWorkflowParameters] =
|
const {
|
||||||
useWorkflowParametersState();
|
parameters: workflowParameters,
|
||||||
|
setParameters: setWorkflowParameters,
|
||||||
|
} = useWorkflowParametersStore();
|
||||||
|
|
||||||
function handleLabelChange(value: string) {
|
function handleLabelChange(value: string) {
|
||||||
const existingLabels = nodes
|
const existingLabels = nodes
|
||||||
|
|||||||
16
skyvern-frontend/src/store/WorkflowParametersStore.ts
Normal file
16
skyvern-frontend/src/store/WorkflowParametersStore.ts
Normal file
@@ -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<WorkflowParametersStore>((set) => {
|
||||||
|
return {
|
||||||
|
parameters: [],
|
||||||
|
setParameters: (parameters: ParametersState) => set({ parameters }),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export { useWorkflowParametersStore };
|
||||||
Reference in New Issue
Block a user