Jon/add input parameters to code block (#2308)
This commit is contained in:
84
skyvern-frontend/src/components/WorkflowBlockInputSet.tsx
Normal file
84
skyvern-frontend/src/components/WorkflowBlockInputSet.tsx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { PlusIcon } from "@radix-ui/react-icons";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
|
import { WorkflowBlockParameterSelect } from "@/routes/workflows/editor/nodes/WorkflowBlockParameterSelect";
|
||||||
|
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<string>) => void;
|
||||||
|
nodeId: string;
|
||||||
|
values: Set<string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function WorkflowBlockInputSet(props: Props) {
|
||||||
|
const { nodeId, onChange, values } = props;
|
||||||
|
const [parameterKeys, setParameterKeys] = useState<Set<string>>(values);
|
||||||
|
const hasKeys = parameterKeys.size > 0;
|
||||||
|
const [workflowParameters] = useWorkflowParametersState();
|
||||||
|
const availableParameterKeys = new Set(
|
||||||
|
workflowParameters.map((parameter) => parameter.key),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onChange(new Set(parameterKeys));
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [parameterKeys]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="workflow-block-input-set relative rounded-md border border-input">
|
||||||
|
<div className="ze-set">
|
||||||
|
{hasKeys ? (
|
||||||
|
Array.from(parameterKeys).map((parameterKey) => {
|
||||||
|
const missing = !availableParameterKeys.has(parameterKey);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={parameterKey}
|
||||||
|
className={`parameter-key flex items-center gap-2 ${missing ? "missing" : ""}`}
|
||||||
|
>
|
||||||
|
<span>{parameterKey}</span>
|
||||||
|
<Cross2Icon
|
||||||
|
className="size-4 cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
setParameterKeys((prev) => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
newSet.delete(parameterKey);
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<span className="flex items-center gap-2 text-slate-400"> </span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="absolute right-0 top-0 flex size-9 cursor-pointer items-center justify-center">
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<div className="rounded p-1 hover:bg-muted">
|
||||||
|
<PlusIcon className="size-4" />
|
||||||
|
</div>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[22rem]">
|
||||||
|
<WorkflowBlockParameterSelect
|
||||||
|
nodeId={nodeId}
|
||||||
|
onAdd={(parameterKey) => {
|
||||||
|
setParameterKeys((prev) => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
newSet.add(parameterKey);
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { WorkflowBlockInputSet };
|
||||||
27
skyvern-frontend/src/components/workflow-block-input-set.css
Normal file
27
skyvern-frontend/src/components/workflow-block-input-set.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
.workflow-block-input-set {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
padding-right: 2.25rem;
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow-block-input-set .ze-set {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow-block-input-set .parameter-key {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
padding: 0.25rem;
|
||||||
|
padding-left: 0.35rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow-block-input-set .parameter-key.missing {
|
||||||
|
background-color: rgba(255, 0, 0, 0.1);
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { WorkflowBlockInputSet } from "@/components/WorkflowBlockInputSet";
|
||||||
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
||||||
import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback";
|
import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback";
|
||||||
import { useNodeLabelChangeHandler } from "@/routes/workflows/hooks/useLabelChangeHandler";
|
import { useNodeLabelChangeHandler } from "@/routes/workflows/hooks/useLabelChangeHandler";
|
||||||
@@ -19,6 +20,7 @@ function CodeBlockNode({ id, data }: NodeProps<CodeBlockNode>) {
|
|||||||
});
|
});
|
||||||
const [inputs, setInputs] = useState({
|
const [inputs, setInputs] = useState({
|
||||||
code: data.code,
|
code: data.code,
|
||||||
|
parameterKeys: data.parameterKeys,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -61,6 +63,20 @@ function CodeBlockNode({ id, data }: NodeProps<CodeBlockNode>) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="text-xs text-slate-300">Input Parameters</Label>
|
||||||
|
<WorkflowBlockInputSet
|
||||||
|
nodeId={id}
|
||||||
|
onChange={(parameterKeys) => {
|
||||||
|
setInputs({
|
||||||
|
...inputs,
|
||||||
|
parameterKeys: Array.from(parameterKeys),
|
||||||
|
});
|
||||||
|
updateNodeData(id, { parameterKeys: Array.from(parameterKeys) });
|
||||||
|
}}
|
||||||
|
values={new Set(inputs.parameterKeys ?? [])}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-xs text-slate-300">Code Input</Label>
|
<Label className="text-xs text-slate-300">Code Input</Label>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
|
|||||||
@@ -3,13 +3,26 @@ import { NodeBaseData } from "../types";
|
|||||||
|
|
||||||
export type CodeBlockNodeData = NodeBaseData & {
|
export type CodeBlockNodeData = NodeBaseData & {
|
||||||
code: string;
|
code: string;
|
||||||
|
parameterKeys: Array<string> | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CodeBlockNode = Node<CodeBlockNodeData, "codeBlock">;
|
export type CodeBlockNode = Node<CodeBlockNodeData, "codeBlock">;
|
||||||
|
|
||||||
|
const codeLead = `
|
||||||
|
# This feature is currently in private beta. Please reach out to
|
||||||
|
# founders@skyvern.com to get access.
|
||||||
|
#
|
||||||
|
# Any parameter you've added to the "Input Parameters" list is available in
|
||||||
|
# global scope, by the same name.
|
||||||
|
#
|
||||||
|
# Any top-level variable you create is assigned to the output of this block.
|
||||||
|
# e.g., if you've written 'x = 5', then 'x' is included in the block output.
|
||||||
|
`;
|
||||||
|
|
||||||
export const codeBlockNodeDefaultData: CodeBlockNodeData = {
|
export const codeBlockNodeDefaultData: CodeBlockNodeData = {
|
||||||
editable: true,
|
editable: true,
|
||||||
label: "",
|
label: "",
|
||||||
code: `# This feature is currently in private beta. Please reach out to founders@skyvern.com to get access\n# All variables will be assigned to the output of this block.\n# Like 'x = 5', 'x' will be assigned to the output of this block.\n\n`,
|
code: codeLead,
|
||||||
continueOnFailure: false,
|
continueOnFailure: false,
|
||||||
|
parameterKeys: null,
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import { AddNodeProps } from "../FlowRenderer";
|
|||||||
import { WorkflowBlockNode } from "../nodes";
|
import { WorkflowBlockNode } from "../nodes";
|
||||||
import { WorkflowBlockIcon } from "../nodes/WorkflowBlockIcon";
|
import { WorkflowBlockIcon } from "../nodes/WorkflowBlockIcon";
|
||||||
|
|
||||||
const enableCodeBlock = import.meta.env.VITE_ENABLE_CODE_BLOCK === "true";
|
const enableCodeBlock =
|
||||||
|
import.meta.env.VITE_ENABLE_CODE_BLOCK?.toLowerCase() === "true";
|
||||||
|
|
||||||
const nodeLibraryItems: Array<{
|
const nodeLibraryItems: Array<{
|
||||||
nodeType: NonNullable<WorkflowBlockNode["type"]>;
|
nodeType: NonNullable<WorkflowBlockNode["type"]>;
|
||||||
|
|||||||
@@ -384,6 +384,7 @@ function convertToNode(
|
|||||||
data: {
|
data: {
|
||||||
...commonData,
|
...commonData,
|
||||||
code: block.code,
|
code: block.code,
|
||||||
|
parameterKeys: block.parameters.map((p) => p.key),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1140,6 +1141,7 @@ function getWorkflowBlock(node: WorkflowBlockNode): BlockYAML {
|
|||||||
return {
|
return {
|
||||||
...base,
|
...base,
|
||||||
block_type: "code",
|
block_type: "code",
|
||||||
|
parameter_keys: node.data.parameterKeys,
|
||||||
code: node.data.code,
|
code: node.data.code,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1842,6 +1844,7 @@ function convertBlocksToBlockYAML(
|
|||||||
...base,
|
...base,
|
||||||
block_type: "code",
|
block_type: "code",
|
||||||
code: block.code,
|
code: block.code,
|
||||||
|
parameter_keys: block.parameters.map((p) => p.key),
|
||||||
};
|
};
|
||||||
return blockYaml;
|
return blockYaml;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user