mirror of
https://github.com/Skyvern-AI/skyvern.git
synced 2025-09-02 10:41:04 +00:00
Add parameter in workflow blocks (#1366)
This commit is contained in:
parent
583075899f
commit
677d85283e
10 changed files with 430 additions and 190 deletions
22
skyvern-frontend/src/components/WorkflowBlockInput.tsx
Normal file
22
skyvern-frontend/src/components/WorkflowBlockInput.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { PlusIcon } from "@radix-ui/react-icons";
|
||||||
|
import { cn } from "@/util/utils";
|
||||||
|
import { Input } from "./ui/input";
|
||||||
|
|
||||||
|
type Props = React.ComponentProps<typeof Input> & {
|
||||||
|
onIconClick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function WorkflowBlockInput(props: Props) {
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<Input {...props} className={cn("pr-9", props.className)} />
|
||||||
|
<div className="absolute right-0 top-0 flex size-9 cursor-pointer items-center justify-center">
|
||||||
|
<div className="rounded p-1 hover:bg-muted" onClick={props.onIconClick}>
|
||||||
|
<PlusIcon className="size-4" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { WorkflowBlockInput };
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { PlusIcon } from "@radix-ui/react-icons";
|
||||||
|
import { cn } from "@/util/utils";
|
||||||
|
import { AutoResizingTextarea } from "./AutoResizingTextarea/AutoResizingTextarea";
|
||||||
|
|
||||||
|
type Props = React.ComponentProps<typeof AutoResizingTextarea> & {
|
||||||
|
onIconClick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function WorkflowBlockInputTextarea(props: Props) {
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<AutoResizingTextarea
|
||||||
|
{...props}
|
||||||
|
className={cn("pr-9", props.className)}
|
||||||
|
/>
|
||||||
|
<div className="absolute right-0 top-0 flex size-9 cursor-pointer items-center justify-center">
|
||||||
|
<div className="rounded p-1 hover:bg-muted" onClick={props.onIconClick}>
|
||||||
|
<PlusIcon className="size-4" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { WorkflowBlockInputTextarea };
|
|
@ -1,4 +1,3 @@
|
||||||
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
|
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionContent,
|
AccordionContent,
|
||||||
|
@ -22,6 +21,8 @@ import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { ClickIcon } from "@/components/icons/ClickIcon";
|
import { ClickIcon } from "@/components/icons/ClickIcon";
|
||||||
import { placeholders, helpTooltips } from "../../helpContent";
|
import { placeholders, helpTooltips } from "../../helpContent";
|
||||||
|
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
||||||
|
import { WorkflowBlockParameterSelect } from "../WorkflowBlockParameterSelect";
|
||||||
|
|
||||||
const urlTooltip =
|
const urlTooltip =
|
||||||
"The URL Skyvern is navigating to. Leave this field blank to pick up from where the last block left off.";
|
"The URL Skyvern is navigating to. Leave this field blank to pick up from where the last block left off.";
|
||||||
|
@ -31,6 +32,9 @@ const navigationGoalTooltip =
|
||||||
const navigationGoalPlaceholder = 'Input {{ name }} into "Name" field.';
|
const navigationGoalPlaceholder = 'Input {{ name }} into "Name" field.';
|
||||||
|
|
||||||
function ActionNode({ id, data }: NodeProps<ActionNode>) {
|
function ActionNode({ id, data }: NodeProps<ActionNode>) {
|
||||||
|
const [parametersPanelField, setParametersPanelField] = useState<
|
||||||
|
string | null
|
||||||
|
>(null);
|
||||||
const { updateNodeData } = useReactFlow();
|
const { updateNodeData } = useReactFlow();
|
||||||
const { editable } = data;
|
const { editable } = data;
|
||||||
const [label, setLabel] = useNodeLabelChangeHandler({
|
const [label, setLabel] = useNodeLabelChangeHandler({
|
||||||
|
@ -102,7 +106,10 @@ function ActionNode({ id, data }: NodeProps<ActionNode>) {
|
||||||
<Label className="text-xs text-slate-300">URL</Label>
|
<Label className="text-xs text-slate-300">URL</Label>
|
||||||
<HelpTooltip content={urlTooltip} />
|
<HelpTooltip content={urlTooltip} />
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("url");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
if (!editable) {
|
||||||
return;
|
return;
|
||||||
|
@ -121,7 +128,10 @@ function ActionNode({ id, data }: NodeProps<ActionNode>) {
|
||||||
</Label>
|
</Label>
|
||||||
<HelpTooltip content={navigationGoalTooltip} />
|
<HelpTooltip content={navigationGoalTooltip} />
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("navigationGoal");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
if (!editable) {
|
||||||
return;
|
return;
|
||||||
|
@ -309,11 +319,11 @@ function ActionNode({ id, data }: NodeProps<ActionNode>) {
|
||||||
content={helpTooltips["action"]["totpVerificationUrl"]}
|
content={helpTooltips["action"]["totpVerificationUrl"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("totpVerificationUrl");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("totpVerificationUrl", event.target.value);
|
handleChange("totpVerificationUrl", event.target.value);
|
||||||
}}
|
}}
|
||||||
value={inputs.totpVerificationUrl ?? ""}
|
value={inputs.totpVerificationUrl ?? ""}
|
||||||
|
@ -330,7 +340,10 @@ function ActionNode({ id, data }: NodeProps<ActionNode>) {
|
||||||
content={helpTooltips["action"]["totpIdentifier"]}
|
content={helpTooltips["action"]["totpIdentifier"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("totpIdentifier");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
if (!editable) {
|
||||||
return;
|
return;
|
||||||
|
@ -347,6 +360,25 @@ function ActionNode({ id, data }: NodeProps<ActionNode>) {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
|
{typeof parametersPanelField === "string" && (
|
||||||
|
<WorkflowBlockParameterSelect
|
||||||
|
nodeId={id}
|
||||||
|
onClose={() => setParametersPanelField(null)}
|
||||||
|
onAdd={(parameterKey) => {
|
||||||
|
if (parametersPanelField === null || !editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (parametersPanelField in inputs) {
|
||||||
|
const currentValue =
|
||||||
|
inputs[parametersPanelField as keyof typeof inputs];
|
||||||
|
handleChange(
|
||||||
|
parametersPanelField,
|
||||||
|
`${currentValue ?? ""}{{ ${parameterKey} }}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
|
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionContent,
|
AccordionContent,
|
||||||
|
@ -23,8 +22,13 @@ import type { ExtractionNode } from "./types";
|
||||||
import { ExtractIcon } from "@/components/icons/ExtractIcon";
|
import { ExtractIcon } from "@/components/icons/ExtractIcon";
|
||||||
|
|
||||||
import { helpTooltips, placeholders } from "../../helpContent";
|
import { helpTooltips, placeholders } from "../../helpContent";
|
||||||
|
import { WorkflowBlockParameterSelect } from "../WorkflowBlockParameterSelect";
|
||||||
|
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
||||||
|
|
||||||
function ExtractionNode({ id, data }: NodeProps<ExtractionNode>) {
|
function ExtractionNode({ id, data }: NodeProps<ExtractionNode>) {
|
||||||
|
const [parametersPanelField, setParametersPanelField] = useState<
|
||||||
|
string | null
|
||||||
|
>(null);
|
||||||
const { updateNodeData } = useReactFlow();
|
const { updateNodeData } = useReactFlow();
|
||||||
const { editable } = data;
|
const { editable } = data;
|
||||||
const [label, setLabel] = useNodeLabelChangeHandler({
|
const [label, setLabel] = useNodeLabelChangeHandler({
|
||||||
|
@ -96,7 +100,10 @@ function ExtractionNode({ id, data }: NodeProps<ExtractionNode>) {
|
||||||
content={helpTooltips["extraction"]["dataExtractionGoal"]}
|
content={helpTooltips["extraction"]["dataExtractionGoal"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("dataExtractionGoal");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
if (!editable) {
|
||||||
return;
|
return;
|
||||||
|
@ -256,6 +263,25 @@ function ExtractionNode({ id, data }: NodeProps<ExtractionNode>) {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
|
{typeof parametersPanelField === "string" && (
|
||||||
|
<WorkflowBlockParameterSelect
|
||||||
|
nodeId={id}
|
||||||
|
onClose={() => setParametersPanelField(null)}
|
||||||
|
onAdd={(parameterKey) => {
|
||||||
|
if (parametersPanelField === null || !editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (parametersPanelField in inputs) {
|
||||||
|
const currentValue =
|
||||||
|
inputs[parametersPanelField as keyof typeof inputs];
|
||||||
|
handleChange(
|
||||||
|
parametersPanelField,
|
||||||
|
`${currentValue ?? ""}{{ ${parameterKey} }}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
|
|
||||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
|
@ -22,6 +21,8 @@ import { NodeActionMenu } from "../NodeActionMenu";
|
||||||
import { errorMappingExampleValue } from "../types";
|
import { errorMappingExampleValue } from "../types";
|
||||||
import type { FileDownloadNode } from "./types";
|
import type { FileDownloadNode } from "./types";
|
||||||
import { helpTooltips, placeholders } from "../../helpContent";
|
import { helpTooltips, placeholders } from "../../helpContent";
|
||||||
|
import { WorkflowBlockParameterSelect } from "../WorkflowBlockParameterSelect";
|
||||||
|
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
||||||
|
|
||||||
const urlTooltip =
|
const urlTooltip =
|
||||||
"The URL Skyvern is navigating to. Leave this field blank to pick up from where the last block left off.";
|
"The URL Skyvern is navigating to. Leave this field blank to pick up from where the last block left off.";
|
||||||
|
@ -31,6 +32,9 @@ const navigationGoalTooltip =
|
||||||
const navigationGoalPlaceholder = "Tell Skyvern which file to download.";
|
const navigationGoalPlaceholder = "Tell Skyvern which file to download.";
|
||||||
|
|
||||||
function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||||
|
const [parametersPanelField, setParametersPanelField] = useState<
|
||||||
|
string | null
|
||||||
|
>(null);
|
||||||
const { updateNodeData } = useReactFlow();
|
const { updateNodeData } = useReactFlow();
|
||||||
const { editable } = data;
|
const { editable } = data;
|
||||||
const [label, setLabel] = useNodeLabelChangeHandler({
|
const [label, setLabel] = useNodeLabelChangeHandler({
|
||||||
|
@ -104,11 +108,11 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||||
<Label className="text-xs text-slate-300">URL</Label>
|
<Label className="text-xs text-slate-300">URL</Label>
|
||||||
<HelpTooltip content={urlTooltip} />
|
<HelpTooltip content={urlTooltip} />
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("url");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("url", event.target.value);
|
handleChange("url", event.target.value);
|
||||||
}}
|
}}
|
||||||
value={inputs.url}
|
value={inputs.url}
|
||||||
|
@ -121,11 +125,11 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||||
<Label className="text-xs text-slate-300">Download Goal</Label>
|
<Label className="text-xs text-slate-300">Download Goal</Label>
|
||||||
<HelpTooltip content={navigationGoalTooltip} />
|
<HelpTooltip content={navigationGoalTooltip} />
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("navigationGoal");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("navigationGoal", event.target.value);
|
handleChange("navigationGoal", event.target.value);
|
||||||
}}
|
}}
|
||||||
value={inputs.navigationGoal}
|
value={inputs.navigationGoal}
|
||||||
|
@ -161,9 +165,6 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||||
min="0"
|
min="0"
|
||||||
value={inputs.maxRetries ?? ""}
|
value={inputs.maxRetries ?? ""}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const value =
|
const value =
|
||||||
event.target.value === ""
|
event.target.value === ""
|
||||||
? null
|
? null
|
||||||
|
@ -188,9 +189,6 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||||
min="0"
|
min="0"
|
||||||
value={inputs.maxStepsOverride ?? ""}
|
value={inputs.maxStepsOverride ?? ""}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const value =
|
const value =
|
||||||
event.target.value === ""
|
event.target.value === ""
|
||||||
? null
|
? null
|
||||||
|
@ -213,9 +211,6 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||||
checked={inputs.errorCodeMapping !== "null"}
|
checked={inputs.errorCodeMapping !== "null"}
|
||||||
disabled={!editable}
|
disabled={!editable}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange(
|
handleChange(
|
||||||
"errorCodeMapping",
|
"errorCodeMapping",
|
||||||
checked
|
checked
|
||||||
|
@ -231,9 +226,6 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||||
language="json"
|
language="json"
|
||||||
value={inputs.errorCodeMapping}
|
value={inputs.errorCodeMapping}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("errorCodeMapping", value);
|
handleChange("errorCodeMapping", value);
|
||||||
}}
|
}}
|
||||||
className="nowheel nopan"
|
className="nowheel nopan"
|
||||||
|
@ -256,9 +248,6 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||||
<Switch
|
<Switch
|
||||||
checked={inputs.continueOnFailure}
|
checked={inputs.continueOnFailure}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("continueOnFailure", checked);
|
handleChange("continueOnFailure", checked);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -277,9 +266,6 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||||
<Switch
|
<Switch
|
||||||
checked={inputs.cacheActions}
|
checked={inputs.cacheActions}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("cacheActions", checked);
|
handleChange("cacheActions", checked);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -301,9 +287,6 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||||
className="nopan w-52 text-xs"
|
className="nopan w-52 text-xs"
|
||||||
value={inputs.downloadSuffix ?? ""}
|
value={inputs.downloadSuffix ?? ""}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("downloadSuffix", event.target.value);
|
handleChange("downloadSuffix", event.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -318,11 +301,11 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||||
content={helpTooltips["download"]["totpVerificationUrl"]}
|
content={helpTooltips["download"]["totpVerificationUrl"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("totpVerificationUrl");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("totpVerificationUrl", event.target.value);
|
handleChange("totpVerificationUrl", event.target.value);
|
||||||
}}
|
}}
|
||||||
value={inputs.totpVerificationUrl ?? ""}
|
value={inputs.totpVerificationUrl ?? ""}
|
||||||
|
@ -341,11 +324,11 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||||
content={helpTooltips["download"]["totpIdentifier"]}
|
content={helpTooltips["download"]["totpIdentifier"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("totpIdentifier");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("totpIdentifier", event.target.value);
|
handleChange("totpIdentifier", event.target.value);
|
||||||
}}
|
}}
|
||||||
value={inputs.totpIdentifier ?? ""}
|
value={inputs.totpIdentifier ?? ""}
|
||||||
|
@ -358,6 +341,25 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
|
{typeof parametersPanelField === "string" && (
|
||||||
|
<WorkflowBlockParameterSelect
|
||||||
|
nodeId={id}
|
||||||
|
onClose={() => setParametersPanelField(null)}
|
||||||
|
onAdd={(parameterKey) => {
|
||||||
|
if (parametersPanelField === null || !editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (parametersPanelField in inputs) {
|
||||||
|
const currentValue =
|
||||||
|
inputs[parametersPanelField as keyof typeof inputs];
|
||||||
|
handleChange(
|
||||||
|
parametersPanelField,
|
||||||
|
`${currentValue ?? ""}{{ ${parameterKey} }}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
|
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionContent,
|
AccordionContent,
|
||||||
|
@ -23,8 +22,13 @@ import type { LoginNode } from "./types";
|
||||||
import { LockOpen1Icon } from "@radix-ui/react-icons";
|
import { LockOpen1Icon } from "@radix-ui/react-icons";
|
||||||
import { CredentialParameterSelector } from "./CredentialParameterSelector";
|
import { CredentialParameterSelector } from "./CredentialParameterSelector";
|
||||||
import { helpTooltips, placeholders } from "../../helpContent";
|
import { helpTooltips, placeholders } from "../../helpContent";
|
||||||
|
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
||||||
|
import { WorkflowBlockParameterSelect } from "../WorkflowBlockParameterSelect";
|
||||||
|
|
||||||
function LoginNode({ id, data }: NodeProps<LoginNode>) {
|
function LoginNode({ id, data }: NodeProps<LoginNode>) {
|
||||||
|
const [parametersPanelField, setParametersPanelField] = useState<
|
||||||
|
string | null
|
||||||
|
>(null);
|
||||||
const { updateNodeData } = useReactFlow();
|
const { updateNodeData } = useReactFlow();
|
||||||
const { editable } = data;
|
const { editable } = data;
|
||||||
const [label, setLabel] = useNodeLabelChangeHandler({
|
const [label, setLabel] = useNodeLabelChangeHandler({
|
||||||
|
@ -53,7 +57,7 @@ function LoginNode({ id, data }: NodeProps<LoginNode>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="relative">
|
||||||
<Handle
|
<Handle
|
||||||
type="source"
|
type="source"
|
||||||
position={Position.Bottom}
|
position={Position.Bottom}
|
||||||
|
@ -95,11 +99,11 @@ function LoginNode({ id, data }: NodeProps<LoginNode>) {
|
||||||
<Label className="text-xs text-slate-300">URL</Label>
|
<Label className="text-xs text-slate-300">URL</Label>
|
||||||
<HelpTooltip content={helpTooltips["login"]["url"]} />
|
<HelpTooltip content={helpTooltips["login"]["url"]} />
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("url");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("url", event.target.value);
|
handleChange("url", event.target.value);
|
||||||
}}
|
}}
|
||||||
value={inputs.url}
|
value={inputs.url}
|
||||||
|
@ -112,11 +116,11 @@ function LoginNode({ id, data }: NodeProps<LoginNode>) {
|
||||||
<Label className="text-xs text-slate-300">Login Goal</Label>
|
<Label className="text-xs text-slate-300">Login Goal</Label>
|
||||||
<HelpTooltip content={helpTooltips["login"]["navigationGoal"]} />
|
<HelpTooltip content={helpTooltips["login"]["navigationGoal"]} />
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("navigationGoal");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("navigationGoal", event.target.value);
|
handleChange("navigationGoal", event.target.value);
|
||||||
}}
|
}}
|
||||||
value={inputs.navigationGoal}
|
value={inputs.navigationGoal}
|
||||||
|
@ -175,9 +179,6 @@ function LoginNode({ id, data }: NodeProps<LoginNode>) {
|
||||||
min="0"
|
min="0"
|
||||||
value={inputs.maxRetries ?? ""}
|
value={inputs.maxRetries ?? ""}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const value =
|
const value =
|
||||||
event.target.value === ""
|
event.target.value === ""
|
||||||
? null
|
? null
|
||||||
|
@ -202,9 +203,6 @@ function LoginNode({ id, data }: NodeProps<LoginNode>) {
|
||||||
min="0"
|
min="0"
|
||||||
value={inputs.maxStepsOverride ?? ""}
|
value={inputs.maxStepsOverride ?? ""}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const value =
|
const value =
|
||||||
event.target.value === ""
|
event.target.value === ""
|
||||||
? null
|
? null
|
||||||
|
@ -227,9 +225,6 @@ function LoginNode({ id, data }: NodeProps<LoginNode>) {
|
||||||
checked={inputs.errorCodeMapping !== "null"}
|
checked={inputs.errorCodeMapping !== "null"}
|
||||||
disabled={!editable}
|
disabled={!editable}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange(
|
handleChange(
|
||||||
"errorCodeMapping",
|
"errorCodeMapping",
|
||||||
checked
|
checked
|
||||||
|
@ -245,9 +240,6 @@ function LoginNode({ id, data }: NodeProps<LoginNode>) {
|
||||||
language="json"
|
language="json"
|
||||||
value={inputs.errorCodeMapping}
|
value={inputs.errorCodeMapping}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("errorCodeMapping", value);
|
handleChange("errorCodeMapping", value);
|
||||||
}}
|
}}
|
||||||
className="nowheel nopan"
|
className="nowheel nopan"
|
||||||
|
@ -270,9 +262,6 @@ function LoginNode({ id, data }: NodeProps<LoginNode>) {
|
||||||
<Switch
|
<Switch
|
||||||
checked={inputs.continueOnFailure}
|
checked={inputs.continueOnFailure}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("continueOnFailure", checked);
|
handleChange("continueOnFailure", checked);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -291,9 +280,6 @@ function LoginNode({ id, data }: NodeProps<LoginNode>) {
|
||||||
<Switch
|
<Switch
|
||||||
checked={inputs.cacheActions}
|
checked={inputs.cacheActions}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("cacheActions", checked);
|
handleChange("cacheActions", checked);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -309,11 +295,11 @@ function LoginNode({ id, data }: NodeProps<LoginNode>) {
|
||||||
content={helpTooltips["login"]["totpVerificationUrl"]}
|
content={helpTooltips["login"]["totpVerificationUrl"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("totpVerificationUrl");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("totpVerificationUrl", event.target.value);
|
handleChange("totpVerificationUrl", event.target.value);
|
||||||
}}
|
}}
|
||||||
value={inputs.totpVerificationUrl ?? ""}
|
value={inputs.totpVerificationUrl ?? ""}
|
||||||
|
@ -330,11 +316,11 @@ function LoginNode({ id, data }: NodeProps<LoginNode>) {
|
||||||
content={helpTooltips["login"]["totpIdentifier"]}
|
content={helpTooltips["login"]["totpIdentifier"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("totpIdentifier");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("totpIdentifier", event.target.value);
|
handleChange("totpIdentifier", event.target.value);
|
||||||
}}
|
}}
|
||||||
value={inputs.totpIdentifier ?? ""}
|
value={inputs.totpIdentifier ?? ""}
|
||||||
|
@ -347,6 +333,25 @@ function LoginNode({ id, data }: NodeProps<LoginNode>) {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
|
{typeof parametersPanelField === "string" && (
|
||||||
|
<WorkflowBlockParameterSelect
|
||||||
|
nodeId={id}
|
||||||
|
onClose={() => setParametersPanelField(null)}
|
||||||
|
onAdd={(parameterKey) => {
|
||||||
|
if (parametersPanelField === null || !editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (parametersPanelField in inputs) {
|
||||||
|
const currentValue =
|
||||||
|
inputs[parametersPanelField as keyof typeof inputs];
|
||||||
|
handleChange(
|
||||||
|
parametersPanelField,
|
||||||
|
`${currentValue ?? ""}{{ ${parameterKey} }}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
|
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionContent,
|
AccordionContent,
|
||||||
|
@ -22,9 +21,16 @@ import { Switch } from "@/components/ui/switch";
|
||||||
import type { NavigationNode } from "./types";
|
import type { NavigationNode } from "./types";
|
||||||
import { RobotIcon } from "@/components/icons/RobotIcon";
|
import { RobotIcon } from "@/components/icons/RobotIcon";
|
||||||
import { helpTooltips, placeholders } from "../../helpContent";
|
import { helpTooltips, placeholders } from "../../helpContent";
|
||||||
|
import { WorkflowBlockParameterSelect } from "../WorkflowBlockParameterSelect";
|
||||||
|
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
||||||
|
import { WorkflowBlockInput } from "@/components/WorkflowBlockInput";
|
||||||
|
|
||||||
function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
||||||
const { updateNodeData } = useReactFlow();
|
const { updateNodeData } = useReactFlow();
|
||||||
|
const [parametersPanelField, setParametersPanelField] = useState<
|
||||||
|
string | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
const { editable } = data;
|
const { editable } = data;
|
||||||
const [label, setLabel] = useNodeLabelChangeHandler({
|
const [label, setLabel] = useNodeLabelChangeHandler({
|
||||||
id,
|
id,
|
||||||
|
@ -54,7 +60,7 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="relative">
|
||||||
<Handle
|
<Handle
|
||||||
type="source"
|
type="source"
|
||||||
position={Position.Bottom}
|
position={Position.Bottom}
|
||||||
|
@ -96,11 +102,9 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
||||||
<Label className="text-xs text-slate-300">URL</Label>
|
<Label className="text-xs text-slate-300">URL</Label>
|
||||||
<HelpTooltip content={helpTooltips["navigation"]["url"]} />
|
<HelpTooltip content={helpTooltips["navigation"]["url"]} />
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => setParametersPanelField("url")}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("url", event.target.value);
|
handleChange("url", event.target.value);
|
||||||
}}
|
}}
|
||||||
value={inputs.url}
|
value={inputs.url}
|
||||||
|
@ -115,11 +119,9 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
||||||
content={helpTooltips["navigation"]["navigationGoal"]}
|
content={helpTooltips["navigation"]["navigationGoal"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => setParametersPanelField("navigationGoal")}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("navigationGoal", event.target.value);
|
handleChange("navigationGoal", event.target.value);
|
||||||
}}
|
}}
|
||||||
value={inputs.navigationGoal}
|
value={inputs.navigationGoal}
|
||||||
|
@ -152,9 +154,6 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
||||||
min="0"
|
min="0"
|
||||||
value={inputs.maxRetries ?? ""}
|
value={inputs.maxRetries ?? ""}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const value =
|
const value =
|
||||||
event.target.value === ""
|
event.target.value === ""
|
||||||
? null
|
? null
|
||||||
|
@ -179,9 +178,6 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
||||||
min="0"
|
min="0"
|
||||||
value={inputs.maxStepsOverride ?? ""}
|
value={inputs.maxStepsOverride ?? ""}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const value =
|
const value =
|
||||||
event.target.value === ""
|
event.target.value === ""
|
||||||
? null
|
? null
|
||||||
|
@ -204,9 +200,6 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
||||||
checked={inputs.errorCodeMapping !== "null"}
|
checked={inputs.errorCodeMapping !== "null"}
|
||||||
disabled={!editable}
|
disabled={!editable}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange(
|
handleChange(
|
||||||
"errorCodeMapping",
|
"errorCodeMapping",
|
||||||
checked
|
checked
|
||||||
|
@ -222,9 +215,6 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
||||||
language="json"
|
language="json"
|
||||||
value={inputs.errorCodeMapping}
|
value={inputs.errorCodeMapping}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("errorCodeMapping", value);
|
handleChange("errorCodeMapping", value);
|
||||||
}}
|
}}
|
||||||
className="nowheel nopan"
|
className="nowheel nopan"
|
||||||
|
@ -247,9 +237,6 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
||||||
<Switch
|
<Switch
|
||||||
checked={inputs.continueOnFailure}
|
checked={inputs.continueOnFailure}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("continueOnFailure", checked);
|
handleChange("continueOnFailure", checked);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -268,9 +255,6 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
||||||
<Switch
|
<Switch
|
||||||
checked={inputs.cacheActions}
|
checked={inputs.cacheActions}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("cacheActions", checked);
|
handleChange("cacheActions", checked);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -290,9 +274,6 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
||||||
<Switch
|
<Switch
|
||||||
checked={inputs.allowDownloads}
|
checked={inputs.allowDownloads}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("allowDownloads", checked);
|
handleChange("allowDownloads", checked);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -307,15 +288,15 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
||||||
content={helpTooltips["navigation"]["fileSuffix"]}
|
content={helpTooltips["navigation"]["fileSuffix"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<WorkflowBlockInput
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("downloadSuffix");
|
||||||
|
}}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={placeholders["navigation"]["downloadSuffix"]}
|
placeholder={placeholders["navigation"]["downloadSuffix"]}
|
||||||
className="nopan w-52 text-xs"
|
className="nopan w-52 text-xs"
|
||||||
value={inputs.downloadSuffix ?? ""}
|
value={inputs.downloadSuffix ?? ""}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("downloadSuffix", event.target.value);
|
handleChange("downloadSuffix", event.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -332,11 +313,11 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("totpVerificationUrl");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("totpVerificationUrl", event.target.value);
|
handleChange("totpVerificationUrl", event.target.value);
|
||||||
}}
|
}}
|
||||||
value={inputs.totpVerificationUrl ?? ""}
|
value={inputs.totpVerificationUrl ?? ""}
|
||||||
|
@ -355,11 +336,11 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
||||||
content={helpTooltips["navigation"]["totpIdentifier"]}
|
content={helpTooltips["navigation"]["totpIdentifier"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("totpIdentifier");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("totpIdentifier", event.target.value);
|
handleChange("totpIdentifier", event.target.value);
|
||||||
}}
|
}}
|
||||||
value={inputs.totpIdentifier ?? ""}
|
value={inputs.totpIdentifier ?? ""}
|
||||||
|
@ -372,6 +353,25 @@ function NavigationNode({ id, data }: NodeProps<NavigationNode>) {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
|
{typeof parametersPanelField === "string" && (
|
||||||
|
<WorkflowBlockParameterSelect
|
||||||
|
nodeId={id}
|
||||||
|
onClose={() => setParametersPanelField(null)}
|
||||||
|
onAdd={(parameterKey) => {
|
||||||
|
if (parametersPanelField === null || !editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (parametersPanelField in inputs) {
|
||||||
|
const currentValue =
|
||||||
|
inputs[parametersPanelField as keyof typeof inputs];
|
||||||
|
handleChange(
|
||||||
|
parametersPanelField,
|
||||||
|
`${currentValue ?? ""}{{ ${parameterKey} }}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
|
|
||||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
|
@ -9,6 +8,7 @@ import {
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
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 { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback";
|
import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback";
|
||||||
|
@ -24,22 +24,28 @@ import {
|
||||||
} from "@xyflow/react";
|
} from "@xyflow/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { AppNode } from "..";
|
import { AppNode } from "..";
|
||||||
|
import { helpTooltips, placeholders } from "../../helpContent";
|
||||||
import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils";
|
import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils";
|
||||||
import { EditableNodeTitle } from "../components/EditableNodeTitle";
|
import { EditableNodeTitle } from "../components/EditableNodeTitle";
|
||||||
import { NodeActionMenu } from "../NodeActionMenu";
|
import { NodeActionMenu } from "../NodeActionMenu";
|
||||||
|
import { dataSchemaExampleValue, errorMappingExampleValue } from "../types";
|
||||||
|
import { WorkflowBlockParameterSelect } from "../WorkflowBlockParameterSelect";
|
||||||
import { ParametersMultiSelect } from "./ParametersMultiSelect";
|
import { ParametersMultiSelect } from "./ParametersMultiSelect";
|
||||||
import type { TaskNode } from "./types";
|
import type { TaskNode } from "./types";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { WorkflowBlockInput } from "@/components/WorkflowBlockInput";
|
||||||
import { dataSchemaExampleValue, errorMappingExampleValue } from "../types";
|
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
||||||
import { helpTooltips, placeholders } from "../../helpContent";
|
|
||||||
|
|
||||||
function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
|
const [parametersPanelField, setParametersPanelField] = useState<
|
||||||
|
string | null
|
||||||
|
>(null);
|
||||||
const { updateNodeData } = useReactFlow();
|
const { updateNodeData } = useReactFlow();
|
||||||
const { editable } = data;
|
const { editable } = data;
|
||||||
const deleteNodeCallback = useDeleteNodeCallback();
|
const deleteNodeCallback = useDeleteNodeCallback();
|
||||||
const nodes = useNodes<AppNode>();
|
const nodes = useNodes<AppNode>();
|
||||||
const edges = useEdges();
|
const edges = useEdges();
|
||||||
const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id);
|
const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id);
|
||||||
|
|
||||||
const [label, setLabel] = useNodeLabelChangeHandler({
|
const [label, setLabel] = useNodeLabelChangeHandler({
|
||||||
id,
|
id,
|
||||||
initialValue: data.label,
|
initialValue: data.label,
|
||||||
|
@ -70,7 +76,7 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="relative">
|
||||||
<Handle
|
<Handle
|
||||||
type="source"
|
type="source"
|
||||||
position={Position.Bottom}
|
position={Position.Bottom}
|
||||||
|
@ -116,12 +122,12 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
<Label className="text-xs text-slate-300">URL</Label>
|
<Label className="text-xs text-slate-300">URL</Label>
|
||||||
<HelpTooltip content={helpTooltips["task"]["url"]} />
|
<HelpTooltip content={helpTooltips["task"]["url"]} />
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
onChange={(event) => {
|
onIconClick={() => {
|
||||||
if (!editable) {
|
setParametersPanelField("url");
|
||||||
return;
|
}}
|
||||||
}
|
onChange={(value) => {
|
||||||
handleChange("url", event.target.value);
|
handleChange("url", value);
|
||||||
}}
|
}}
|
||||||
value={inputs.url}
|
value={inputs.url}
|
||||||
placeholder={placeholders["task"]["url"]}
|
placeholder={placeholders["task"]["url"]}
|
||||||
|
@ -135,12 +141,12 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
content={helpTooltips["task"]["navigationGoal"]}
|
content={helpTooltips["task"]["navigationGoal"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
onChange={(event) => {
|
onIconClick={() => {
|
||||||
if (!editable) {
|
setParametersPanelField("navigationGoal");
|
||||||
return;
|
}}
|
||||||
}
|
onChange={(value) => {
|
||||||
handleChange("navigationGoal", event.target.value);
|
handleChange("navigationGoal", value);
|
||||||
}}
|
}}
|
||||||
value={inputs.navigationGoal}
|
value={inputs.navigationGoal}
|
||||||
placeholder={placeholders["task"]["navigationGoal"]}
|
placeholder={placeholders["task"]["navigationGoal"]}
|
||||||
|
@ -172,11 +178,11 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
content={helpTooltips["task"]["dataExtractionGoal"]}
|
content={helpTooltips["task"]["dataExtractionGoal"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("dataExtractionGoal");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("dataExtractionGoal", event.target.value);
|
handleChange("dataExtractionGoal", event.target.value);
|
||||||
}}
|
}}
|
||||||
value={inputs.dataExtractionGoal}
|
value={inputs.dataExtractionGoal}
|
||||||
|
@ -197,9 +203,6 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={inputs.dataSchema !== "null"}
|
checked={inputs.dataSchema !== "null"}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange(
|
handleChange(
|
||||||
"dataSchema",
|
"dataSchema",
|
||||||
checked
|
checked
|
||||||
|
@ -215,9 +218,6 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
language="json"
|
language="json"
|
||||||
value={inputs.dataSchema}
|
value={inputs.dataSchema}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("dataSchema", value);
|
handleChange("dataSchema", value);
|
||||||
}}
|
}}
|
||||||
className="nowheel nopan"
|
className="nowheel nopan"
|
||||||
|
@ -247,9 +247,6 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
min="0"
|
min="0"
|
||||||
value={inputs.maxRetries ?? ""}
|
value={inputs.maxRetries ?? ""}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const value =
|
const value =
|
||||||
event.target.value === ""
|
event.target.value === ""
|
||||||
? null
|
? null
|
||||||
|
@ -274,9 +271,6 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
min="0"
|
min="0"
|
||||||
value={inputs.maxStepsOverride ?? ""}
|
value={inputs.maxStepsOverride ?? ""}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const value =
|
const value =
|
||||||
event.target.value === ""
|
event.target.value === ""
|
||||||
? null
|
? null
|
||||||
|
@ -299,9 +293,6 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
checked={inputs.errorCodeMapping !== "null"}
|
checked={inputs.errorCodeMapping !== "null"}
|
||||||
disabled={!editable}
|
disabled={!editable}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange(
|
handleChange(
|
||||||
"errorCodeMapping",
|
"errorCodeMapping",
|
||||||
checked
|
checked
|
||||||
|
@ -317,9 +308,6 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
language="json"
|
language="json"
|
||||||
value={inputs.errorCodeMapping}
|
value={inputs.errorCodeMapping}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("errorCodeMapping", value);
|
handleChange("errorCodeMapping", value);
|
||||||
}}
|
}}
|
||||||
className="nowheel nopan"
|
className="nowheel nopan"
|
||||||
|
@ -342,9 +330,6 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
<Switch
|
<Switch
|
||||||
checked={inputs.continueOnFailure}
|
checked={inputs.continueOnFailure}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("continueOnFailure", checked);
|
handleChange("continueOnFailure", checked);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -363,9 +348,6 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
<Switch
|
<Switch
|
||||||
checked={inputs.cacheActions}
|
checked={inputs.cacheActions}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("cacheActions", checked);
|
handleChange("cacheActions", checked);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -385,9 +367,6 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
<Switch
|
<Switch
|
||||||
checked={inputs.allowDownloads}
|
checked={inputs.allowDownloads}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("allowDownloads", checked);
|
handleChange("allowDownloads", checked);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -400,15 +379,15 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
</Label>
|
</Label>
|
||||||
<HelpTooltip content={helpTooltips["task"]["fileSuffix"]} />
|
<HelpTooltip content={helpTooltips["task"]["fileSuffix"]} />
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<WorkflowBlockInput
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("downloadSuffix");
|
||||||
|
}}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={placeholders["task"]["downloadSuffix"]}
|
placeholder={placeholders["task"]["downloadSuffix"]}
|
||||||
className="nopan w-52 text-xs"
|
className="nopan w-52 text-xs"
|
||||||
value={inputs.downloadSuffix ?? ""}
|
value={inputs.downloadSuffix ?? ""}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("downloadSuffix", event.target.value);
|
handleChange("downloadSuffix", event.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -423,11 +402,11 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
content={helpTooltips["task"]["totpVerificationUrl"]}
|
content={helpTooltips["task"]["totpVerificationUrl"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("totpVerificationUrl");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("totpVerificationUrl", event.target.value);
|
handleChange("totpVerificationUrl", event.target.value);
|
||||||
}}
|
}}
|
||||||
value={inputs.totpVerificationUrl ?? ""}
|
value={inputs.totpVerificationUrl ?? ""}
|
||||||
|
@ -444,11 +423,11 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
content={helpTooltips["task"]["totpIdentifier"]}
|
content={helpTooltips["task"]["totpIdentifier"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("totpIdentifier");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("totpIdentifier", event.target.value);
|
handleChange("totpIdentifier", event.target.value);
|
||||||
}}
|
}}
|
||||||
value={inputs.totpIdentifier ?? ""}
|
value={inputs.totpIdentifier ?? ""}
|
||||||
|
@ -461,6 +440,25 @@ function TaskNode({ id, data }: NodeProps<TaskNode>) {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
|
{typeof parametersPanelField === "string" && (
|
||||||
|
<WorkflowBlockParameterSelect
|
||||||
|
nodeId={id}
|
||||||
|
onClose={() => setParametersPanelField(null)}
|
||||||
|
onAdd={(parameterKey) => {
|
||||||
|
if (parametersPanelField === null || !editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (parametersPanelField in inputs) {
|
||||||
|
const currentValue =
|
||||||
|
inputs[parametersPanelField as keyof typeof inputs];
|
||||||
|
handleChange(
|
||||||
|
parametersPanelField,
|
||||||
|
`${currentValue ?? ""}{{ ${parameterKey} }}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { EditableNodeTitle } from "../components/EditableNodeTitle";
|
||||||
import { NodeActionMenu } from "../NodeActionMenu";
|
import { NodeActionMenu } from "../NodeActionMenu";
|
||||||
import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback";
|
import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
|
|
||||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { errorMappingExampleValue } from "../types";
|
import { errorMappingExampleValue } from "../types";
|
||||||
|
@ -21,8 +20,13 @@ import {
|
||||||
} from "@/components/ui/accordion";
|
} from "@/components/ui/accordion";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { helpTooltips } from "../../helpContent";
|
import { helpTooltips } from "../../helpContent";
|
||||||
|
import { WorkflowBlockParameterSelect } from "../WorkflowBlockParameterSelect";
|
||||||
|
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
||||||
|
|
||||||
function ValidationNode({ id, data }: NodeProps<ValidationNode>) {
|
function ValidationNode({ id, data }: NodeProps<ValidationNode>) {
|
||||||
|
const [parametersPanelField, setParametersPanelField] = useState<
|
||||||
|
string | null
|
||||||
|
>(null);
|
||||||
const { updateNodeData } = useReactFlow();
|
const { updateNodeData } = useReactFlow();
|
||||||
const { editable } = data;
|
const { editable } = data;
|
||||||
const [label, setLabel] = useNodeLabelChangeHandler({
|
const [label, setLabel] = useNodeLabelChangeHandler({
|
||||||
|
@ -83,7 +87,10 @@ function ValidationNode({ id, data }: NodeProps<ValidationNode>) {
|
||||||
</header>
|
</header>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-xs text-slate-300">Complete if...</Label>
|
<Label className="text-xs text-slate-300">Complete if...</Label>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("completeCriterion");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
if (!editable) {
|
||||||
return;
|
return;
|
||||||
|
@ -96,7 +103,10 @@ function ValidationNode({ id, data }: NodeProps<ValidationNode>) {
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-xs text-slate-300">Terminate if...</Label>
|
<Label className="text-xs text-slate-300">Terminate if...</Label>
|
||||||
<AutoResizingTextarea
|
<WorkflowBlockInputTextarea
|
||||||
|
onIconClick={() => {
|
||||||
|
setParametersPanelField("terminateCriterion");
|
||||||
|
}}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!editable) {
|
if (!editable) {
|
||||||
return;
|
return;
|
||||||
|
@ -185,6 +195,25 @@ function ValidationNode({ id, data }: NodeProps<ValidationNode>) {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
|
{typeof parametersPanelField === "string" && (
|
||||||
|
<WorkflowBlockParameterSelect
|
||||||
|
nodeId={id}
|
||||||
|
onClose={() => setParametersPanelField(null)}
|
||||||
|
onAdd={(parameterKey) => {
|
||||||
|
if (parametersPanelField === null || !editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (parametersPanelField in inputs) {
|
||||||
|
const currentValue =
|
||||||
|
inputs[parametersPanelField as keyof typeof inputs];
|
||||||
|
handleChange(
|
||||||
|
parametersPanelField,
|
||||||
|
`${currentValue ?? ""}{{ ${parameterKey} }}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
import { useEdges, useNodes } from "@xyflow/react";
|
||||||
|
import { useWorkflowParametersState } from "../useWorkflowParametersState";
|
||||||
|
import { AppNode } from ".";
|
||||||
|
import { getAvailableOutputParameterKeys } from "../workflowEditorUtils";
|
||||||
|
import { Cross2Icon, PlusIcon } from "@radix-ui/react-icons";
|
||||||
|
import { SwitchBar } from "@/components/SwitchBar";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import { ScrollAreaViewport } from "@radix-ui/react-scroll-area";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
nodeId: string;
|
||||||
|
onClose: () => void;
|
||||||
|
onAdd: (parameterKey: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function WorkflowBlockParameterSelect({ nodeId, onClose, onAdd }: Props) {
|
||||||
|
const [content, setContent] = useState("parameters");
|
||||||
|
const [workflowParameters] = useWorkflowParametersState();
|
||||||
|
const nodes = useNodes<AppNode>();
|
||||||
|
const edges = useEdges();
|
||||||
|
const outputParameterKeys = getAvailableOutputParameterKeys(
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
nodeId,
|
||||||
|
);
|
||||||
|
const workflowParameterKeys = workflowParameters.map(
|
||||||
|
(parameter) => parameter.key,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="nopan nowheel absolute right-[-296px] top-0 mt-0 w-[280px] cursor-auto space-y-3 rounded-md border border-slate-700 bg-slate-950 p-4">
|
||||||
|
<header className="flex justify-between">
|
||||||
|
<h1>Add Parameter</h1>
|
||||||
|
<Cross2Icon className="size-6 cursor-pointer" onClick={onClose} />
|
||||||
|
</header>
|
||||||
|
<SwitchBar
|
||||||
|
onChange={(value) => setContent(value)}
|
||||||
|
value={content}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: "Parameters",
|
||||||
|
value: "parameters",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Block Outputs",
|
||||||
|
value: "outputs",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<ScrollArea>
|
||||||
|
<ScrollAreaViewport className="max-h-96">
|
||||||
|
{content === "parameters" && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{workflowParameterKeys.map((parameterKey) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={parameterKey}
|
||||||
|
className="flex cursor-pointer justify-between rounded-md bg-slate-elevation1 px-3 py-2 text-xs hover:bg-slate-elevation2"
|
||||||
|
onClick={() => {
|
||||||
|
onAdd(parameterKey);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{parameterKey}
|
||||||
|
<PlusIcon />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{workflowParameterKeys.length === 0 && (
|
||||||
|
<div className="text-xs">No workflow parameters</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{content === "outputs" && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{outputParameterKeys.map((parameterKey) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={parameterKey}
|
||||||
|
className="flex cursor-pointer justify-between rounded-md bg-slate-elevation1 px-3 py-2 text-xs hover:bg-slate-elevation2"
|
||||||
|
onClick={() => {
|
||||||
|
onAdd(parameterKey);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{parameterKey}
|
||||||
|
<PlusIcon />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{outputParameterKeys.length === 0 && (
|
||||||
|
<div className="text-xs">No output parameters</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ScrollAreaViewport>
|
||||||
|
</ScrollArea>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { WorkflowBlockParameterSelect };
|
Loading…
Add table
Reference in a new issue