Make output parameters available (#825)
This commit is contained in:
@@ -30,6 +30,7 @@ import "./reactFlowOverrideStyles.css";
|
|||||||
import {
|
import {
|
||||||
createNode,
|
createNode,
|
||||||
generateNodeLabel,
|
generateNodeLabel,
|
||||||
|
getOutputParameterKey,
|
||||||
getWorkflowBlocks,
|
getWorkflowBlocks,
|
||||||
layout,
|
layout,
|
||||||
} from "./workflowEditorUtils";
|
} from "./workflowEditorUtils";
|
||||||
@@ -228,6 +229,7 @@ function FlowRenderer({
|
|||||||
if (!node) {
|
if (!node) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const deletedNodeLabel = node.data.label;
|
||||||
const newNodes = nodes.filter((node) => node.id !== id);
|
const newNodes = nodes.filter((node) => node.id !== id);
|
||||||
const newEdges = edges.flatMap((edge) => {
|
const newEdges = edges.flatMap((edge) => {
|
||||||
if (edge.source === id) {
|
if (edge.source === id) {
|
||||||
@@ -257,7 +259,24 @@ function FlowRenderer({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
doLayout(newNodes, newEdges);
|
// if any node was using the output parameter of the deleted node, remove it from their parameter keys
|
||||||
|
const newNodesWithUpdatedParameters = newNodes.map((node) => {
|
||||||
|
if (node.type === "task") {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
data: {
|
||||||
|
...node.data,
|
||||||
|
parameterKeys: node.data.parameterKeys.filter(
|
||||||
|
(parameter) =>
|
||||||
|
parameter !== getOutputParameterKey(deletedNodeLabel),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
|
||||||
|
doLayout(newNodesWithUpdatedParameters, newEdges);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -17,7 +17,15 @@ import {
|
|||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
||||||
import { ListBulletIcon, MixerVerticalIcon } from "@radix-ui/react-icons";
|
import { ListBulletIcon, MixerVerticalIcon } from "@radix-ui/react-icons";
|
||||||
import { Handle, NodeProps, Position, useReactFlow } from "@xyflow/react";
|
import {
|
||||||
|
Edge,
|
||||||
|
Handle,
|
||||||
|
NodeProps,
|
||||||
|
Position,
|
||||||
|
useEdges,
|
||||||
|
useNodes,
|
||||||
|
useReactFlow,
|
||||||
|
} from "@xyflow/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { EditableNodeTitle } from "../components/EditableNodeTitle";
|
import { EditableNodeTitle } from "../components/EditableNodeTitle";
|
||||||
import { NodeActionMenu } from "../NodeActionMenu";
|
import { NodeActionMenu } from "../NodeActionMenu";
|
||||||
@@ -25,12 +33,49 @@ import { TaskNodeDisplayModeSwitch } from "./TaskNodeDisplayModeSwitch";
|
|||||||
import { TaskNodeParametersPanel } from "./TaskNodeParametersPanel";
|
import { TaskNodeParametersPanel } from "./TaskNodeParametersPanel";
|
||||||
import type { TaskNode, TaskNodeDisplayMode } from "./types";
|
import type { TaskNode, TaskNodeDisplayMode } from "./types";
|
||||||
import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback";
|
import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback";
|
||||||
|
import { AppNode } from "..";
|
||||||
|
import { getOutputParameterKey } from "../../workflowEditorUtils";
|
||||||
|
|
||||||
|
function getPreviousNodeIds(
|
||||||
|
nodes: Array<AppNode>,
|
||||||
|
edges: Array<Edge>,
|
||||||
|
target: string,
|
||||||
|
): Array<string> {
|
||||||
|
const nodeIds: string[] = [];
|
||||||
|
const node = nodes.find((node) => node.id === target);
|
||||||
|
if (!node) {
|
||||||
|
return nodeIds;
|
||||||
|
}
|
||||||
|
let current = edges.find((edge) => edge.target === target);
|
||||||
|
if (current) {
|
||||||
|
while (current) {
|
||||||
|
nodeIds.push(current.source);
|
||||||
|
current = edges.find((edge) => edge.target === current!.source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!node.parentId) {
|
||||||
|
return nodeIds;
|
||||||
|
}
|
||||||
|
return [...nodeIds, ...getPreviousNodeIds(nodes, edges, node.parentId)];
|
||||||
|
}
|
||||||
|
|
||||||
function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
const { updateNodeData } = useReactFlow();
|
const { updateNodeData } = useReactFlow();
|
||||||
const [displayMode, setDisplayMode] = useState<TaskNodeDisplayMode>("basic");
|
const [displayMode, setDisplayMode] = useState<TaskNodeDisplayMode>("basic");
|
||||||
const { editable } = data;
|
const { editable } = data;
|
||||||
const deleteNodeCallback = useDeleteNodeCallback();
|
const deleteNodeCallback = useDeleteNodeCallback();
|
||||||
|
const nodes = useNodes<AppNode>();
|
||||||
|
const edges = useEdges();
|
||||||
|
const previousNodeIds = getPreviousNodeIds(nodes, edges, id);
|
||||||
|
const previousNodes = nodes.filter((node) =>
|
||||||
|
previousNodeIds.includes(node.id),
|
||||||
|
);
|
||||||
|
const labels = previousNodes
|
||||||
|
.filter((node) => node.type !== "nodeAdder")
|
||||||
|
.map((node) => node.data.label);
|
||||||
|
const outputParameterKeys = labels.map((label) =>
|
||||||
|
getOutputParameterKey(label),
|
||||||
|
);
|
||||||
|
|
||||||
const [label, setLabel] = useState(data.label);
|
const [label, setLabel] = useState(data.label);
|
||||||
const [inputs, setInputs] = useState({
|
const [inputs, setInputs] = useState({
|
||||||
@@ -387,6 +432,7 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
|||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-72">
|
<PopoverContent className="w-72">
|
||||||
<TaskNodeParametersPanel
|
<TaskNodeParametersPanel
|
||||||
|
availableOutputParameters={outputParameterKeys}
|
||||||
parameters={data.parameterKeys}
|
parameters={data.parameterKeys}
|
||||||
onParametersChange={(parameterKeys) => {
|
onParametersChange={(parameterKeys) => {
|
||||||
updateNodeData(id, { parameterKeys });
|
updateNodeData(id, { parameterKeys });
|
||||||
|
|||||||
@@ -2,13 +2,20 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||||||
import { useWorkflowParametersState } from "../../useWorkflowParametersState";
|
import { useWorkflowParametersState } from "../../useWorkflowParametersState";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
availableOutputParameters: Array<string>;
|
||||||
parameters: Array<string>;
|
parameters: Array<string>;
|
||||||
onParametersChange: (parameters: Array<string>) => void;
|
onParametersChange: (parameters: Array<string>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
function TaskNodeParametersPanel({ parameters, onParametersChange }: Props) {
|
function TaskNodeParametersPanel({
|
||||||
|
availableOutputParameters,
|
||||||
|
parameters,
|
||||||
|
onParametersChange,
|
||||||
|
}: Props) {
|
||||||
const [workflowParameters] = useWorkflowParametersState();
|
const [workflowParameters] = useWorkflowParametersState();
|
||||||
|
const keys = workflowParameters
|
||||||
|
.map((parameter) => parameter.key)
|
||||||
|
.concat(availableOutputParameters);
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<header className="space-y-1">
|
<header className="space-y-1">
|
||||||
@@ -18,27 +25,25 @@ function TaskNodeParametersPanel({ parameters, onParametersChange }: Props) {
|
|||||||
</span>
|
</span>
|
||||||
</header>
|
</header>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{workflowParameters.map((workflowParameter) => {
|
{keys.map((key) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={workflowParameter.key}
|
key={key}
|
||||||
className="flex items-center gap-2 rounded-sm bg-slate-elevation1 px-3 py-2"
|
className="flex items-center gap-2 rounded-sm bg-slate-elevation1 px-3 py-2"
|
||||||
>
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={parameters.includes(workflowParameter.key)}
|
checked={parameters.includes(key)}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
onParametersChange([...parameters, workflowParameter.key]);
|
onParametersChange([...parameters, key]);
|
||||||
} else {
|
} else {
|
||||||
onParametersChange(
|
onParametersChange(
|
||||||
parameters.filter(
|
parameters.filter((parameterKey) => parameterKey !== key),
|
||||||
(parameter) => parameter !== workflowParameter.key,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="text-xs">{workflowParameter.key}</span>
|
<span className="text-xs">{key}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -616,6 +616,10 @@ function convertEchoParameters(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOutputParameterKey(label: string) {
|
||||||
|
return label + "_output";
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
createNode,
|
createNode,
|
||||||
generateNodeData,
|
generateNodeData,
|
||||||
@@ -624,4 +628,5 @@ export {
|
|||||||
layout,
|
layout,
|
||||||
generateNodeLabel,
|
generateNodeLabel,
|
||||||
convertEchoParameters,
|
convertEchoParameters,
|
||||||
|
getOutputParameterKey,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user