make other script enabled blocks flip-to-script; add type-checked lis… (#3179)
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Flippable } from "@/components/Flippable";
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionContent,
|
AccordionContent,
|
||||||
@@ -23,6 +25,7 @@ import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
|||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { placeholders, helpTooltips } from "../../helpContent";
|
import { placeholders, helpTooltips } from "../../helpContent";
|
||||||
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
||||||
|
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
|
||||||
import { WorkflowBlockInput } from "@/components/WorkflowBlockInput";
|
import { WorkflowBlockInput } from "@/components/WorkflowBlockInput";
|
||||||
import { AppNode } from "..";
|
import { AppNode } from "..";
|
||||||
import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils";
|
import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils";
|
||||||
@@ -31,6 +34,7 @@ import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow"
|
|||||||
import { RunEngineSelector } from "@/components/EngineSelector";
|
import { RunEngineSelector } from "@/components/EngineSelector";
|
||||||
import { ModelSelector } from "@/components/ModelSelector";
|
import { ModelSelector } from "@/components/ModelSelector";
|
||||||
import { useDebugStore } from "@/store/useDebugStore";
|
import { useDebugStore } from "@/store/useDebugStore";
|
||||||
|
import { useBlockScriptStore } from "@/store/BlockScriptStore";
|
||||||
import { cn } from "@/util/utils";
|
import { cn } from "@/util/utils";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { NodeHeader } from "../components/NodeHeader";
|
import { NodeHeader } from "../components/NodeHeader";
|
||||||
@@ -44,7 +48,10 @@ const navigationGoalPlaceholder = 'Input {{ name }} into "Name" field.';
|
|||||||
|
|
||||||
function ActionNode({ id, data, type }: NodeProps<ActionNode>) {
|
function ActionNode({ id, data, type }: NodeProps<ActionNode>) {
|
||||||
const { updateNodeData } = useReactFlow();
|
const { updateNodeData } = useReactFlow();
|
||||||
|
const [facing, setFacing] = useState<"front" | "back">("front");
|
||||||
|
const blockScriptStore = useBlockScriptStore();
|
||||||
const { editable, debuggable, label } = data;
|
const { editable, debuggable, label } = data;
|
||||||
|
const script = blockScriptStore.scripts[label];
|
||||||
const [inputs, setInputs] = useState({
|
const [inputs, setInputs] = useState({
|
||||||
url: data.url,
|
url: data.url,
|
||||||
navigationGoal: data.navigationGoal,
|
navigationGoal: data.navigationGoal,
|
||||||
@@ -78,308 +85,319 @@ function ActionNode({ id, data, type }: NodeProps<ActionNode>) {
|
|||||||
|
|
||||||
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
|
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFacing(data.showCode ? "back" : "front");
|
||||||
|
}, [data.showCode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Flippable facing={facing} preserveFrontsideHeight={true}>
|
||||||
<Handle
|
<div>
|
||||||
type="source"
|
<Handle
|
||||||
position={Position.Bottom}
|
type="source"
|
||||||
id="a"
|
position={Position.Bottom}
|
||||||
className="opacity-0"
|
id="a"
|
||||||
/>
|
className="opacity-0"
|
||||||
<Handle
|
/>
|
||||||
type="target"
|
<Handle
|
||||||
position={Position.Top}
|
type="target"
|
||||||
id="b"
|
position={Position.Top}
|
||||||
className="opacity-0"
|
id="b"
|
||||||
/>
|
className="opacity-0"
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
|
||||||
{
|
|
||||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
|
||||||
thisBlockIsPlaying,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<NodeHeader
|
|
||||||
blockLabel={label}
|
|
||||||
disabled={elideFromDebugging}
|
|
||||||
editable={editable}
|
|
||||||
nodeId={id}
|
|
||||||
totpIdentifier={inputs.totpIdentifier}
|
|
||||||
totpUrl={inputs.totpVerificationUrl}
|
|
||||||
type={type}
|
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={cn("space-y-4", {
|
className={cn(
|
||||||
"opacity-50": thisBlockIsPlaying,
|
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||||
})}
|
{
|
||||||
|
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||||
|
thisBlockIsPlaying,
|
||||||
|
},
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className="space-y-2">
|
<NodeHeader
|
||||||
<div className="flex justify-between">
|
blockLabel={label}
|
||||||
<div className="flex gap-2">
|
disabled={elideFromDebugging}
|
||||||
<Label className="text-xs text-slate-300">URL</Label>
|
editable={editable}
|
||||||
<HelpTooltip content={urlTooltip} />
|
nodeId={id}
|
||||||
</div>
|
totpIdentifier={inputs.totpIdentifier}
|
||||||
{isFirstWorkflowBlock ? (
|
totpUrl={inputs.totpVerificationUrl}
|
||||||
<div className="flex justify-end text-xs text-slate-400">
|
type={type}
|
||||||
Tip: Use the {"+"} button to add parameters!
|
/>
|
||||||
|
<div
|
||||||
|
className={cn("space-y-4", {
|
||||||
|
"opacity-50": thisBlockIsPlaying,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs text-slate-300">URL</Label>
|
||||||
|
<HelpTooltip content={urlTooltip} />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
{isFirstWorkflowBlock ? (
|
||||||
</div>
|
<div className="flex justify-end text-xs text-slate-400">
|
||||||
|
Tip: Use the {"+"} button to add parameters!
|
||||||
<WorkflowBlockInputTextarea
|
|
||||||
canWriteTitle={true}
|
|
||||||
nodeId={id}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("url", value);
|
|
||||||
}}
|
|
||||||
value={inputs.url}
|
|
||||||
placeholder={placeholders["action"]["url"]}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs text-slate-300">
|
|
||||||
Action Instruction
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip content={navigationGoalTooltip} />
|
|
||||||
</div>
|
|
||||||
<WorkflowBlockInputTextarea
|
|
||||||
nodeId={id}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("navigationGoal", value);
|
|
||||||
}}
|
|
||||||
value={inputs.navigationGoal}
|
|
||||||
placeholder={navigationGoalPlaceholder}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-md bg-slate-800 p-2">
|
|
||||||
<div className="space-y-1 text-xs text-slate-400">
|
|
||||||
Tip: While executing the action block, Skyvern will only take one
|
|
||||||
action.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
|
||||||
<Accordion
|
|
||||||
className={cn({
|
|
||||||
"pointer-events-none opacity-50": thisBlockIsPlaying,
|
|
||||||
})}
|
|
||||||
type="single"
|
|
||||||
collapsible
|
|
||||||
>
|
|
||||||
<AccordionItem value="advanced" className="border-b-0">
|
|
||||||
<AccordionTrigger className="py-0">
|
|
||||||
Advanced Settings
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="pl-6 pr-1 pt-1">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<ModelSelector
|
|
||||||
className="nopan w-52 text-xs"
|
|
||||||
value={inputs.model}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("model", value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ParametersMultiSelect
|
|
||||||
availableOutputParameters={outputParameterKeys}
|
|
||||||
parameters={data.parameterKeys}
|
|
||||||
onParametersChange={(parameterKeys) => {
|
|
||||||
updateNodeData(id, { parameterKeys });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
|
||||||
Engine
|
|
||||||
</Label>
|
|
||||||
</div>
|
</div>
|
||||||
<RunEngineSelector
|
) : null}
|
||||||
value={inputs.engine}
|
</div>
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("engine", value);
|
<WorkflowBlockInputTextarea
|
||||||
}}
|
canWriteTitle={true}
|
||||||
className="nopan w-52 text-xs"
|
nodeId={id}
|
||||||
/>
|
onChange={(value) => {
|
||||||
</div>
|
handleChange("url", value);
|
||||||
<div className="space-y-2">
|
}}
|
||||||
<div className="flex gap-4">
|
value={inputs.url}
|
||||||
<div className="flex gap-2">
|
placeholder={placeholders["action"]["url"]}
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
className="nopan text-xs"
|
||||||
Error Messages
|
/>
|
||||||
</Label>
|
</div>
|
||||||
<HelpTooltip
|
<div className="space-y-2">
|
||||||
content={helpTooltips["action"]["errorCodeMapping"]}
|
<div className="flex gap-2">
|
||||||
/>
|
<Label className="text-xs text-slate-300">
|
||||||
</div>
|
Action Instruction
|
||||||
<Checkbox
|
</Label>
|
||||||
checked={inputs.errorCodeMapping !== "null"}
|
<HelpTooltip content={navigationGoalTooltip} />
|
||||||
disabled={!editable}
|
</div>
|
||||||
onCheckedChange={(checked) => {
|
<WorkflowBlockInputTextarea
|
||||||
if (!editable) {
|
nodeId={id}
|
||||||
return;
|
onChange={(value) => {
|
||||||
}
|
handleChange("navigationGoal", value);
|
||||||
handleChange(
|
}}
|
||||||
"errorCodeMapping",
|
value={inputs.navigationGoal}
|
||||||
checked
|
placeholder={navigationGoalPlaceholder}
|
||||||
? JSON.stringify(errorMappingExampleValue, null, 2)
|
className="nopan text-xs"
|
||||||
: "null",
|
/>
|
||||||
);
|
</div>
|
||||||
|
<div className="rounded-md bg-slate-800 p-2">
|
||||||
|
<div className="space-y-1 text-xs text-slate-400">
|
||||||
|
Tip: While executing the action block, Skyvern will only take
|
||||||
|
one action.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<Accordion
|
||||||
|
className={cn({
|
||||||
|
"pointer-events-none opacity-50": thisBlockIsPlaying,
|
||||||
|
})}
|
||||||
|
type="single"
|
||||||
|
collapsible
|
||||||
|
>
|
||||||
|
<AccordionItem value="advanced" className="border-b-0">
|
||||||
|
<AccordionTrigger className="py-0">
|
||||||
|
Advanced Settings
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="pl-6 pr-1 pt-1">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<ModelSelector
|
||||||
|
className="nopan w-52 text-xs"
|
||||||
|
value={inputs.model}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("model", value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ParametersMultiSelect
|
||||||
|
availableOutputParameters={outputParameterKeys}
|
||||||
|
parameters={data.parameterKeys}
|
||||||
|
onParametersChange={(parameterKeys) => {
|
||||||
|
updateNodeData(id, { parameterKeys });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{inputs.errorCodeMapping !== "null" && (
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div className="flex gap-2">
|
||||||
<CodeEditor
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
language="json"
|
Engine
|
||||||
value={inputs.errorCodeMapping}
|
</Label>
|
||||||
onChange={(value) => {
|
</div>
|
||||||
|
<RunEngineSelector
|
||||||
|
value={inputs.engine}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("engine", value);
|
||||||
|
}}
|
||||||
|
className="nopan w-52 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
Error Messages
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["action"]["errorCodeMapping"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
checked={inputs.errorCodeMapping !== "null"}
|
||||||
|
disabled={!editable}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
if (!editable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleChange("errorCodeMapping", value);
|
handleChange(
|
||||||
|
"errorCodeMapping",
|
||||||
|
checked
|
||||||
|
? JSON.stringify(
|
||||||
|
errorMappingExampleValue,
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
: "null",
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
className="nowheel nopan"
|
|
||||||
fontSize={8}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
{inputs.errorCodeMapping !== "null" && (
|
||||||
</div>
|
<div>
|
||||||
<Separator />
|
<CodeEditor
|
||||||
<div className="flex items-center justify-between">
|
language="json"
|
||||||
<div className="flex gap-2">
|
value={inputs.errorCodeMapping}
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
onChange={(value) => {
|
||||||
Continue on Failure
|
if (!editable) {
|
||||||
</Label>
|
return;
|
||||||
<HelpTooltip
|
}
|
||||||
content={helpTooltips["action"]["continueOnFailure"]}
|
handleChange("errorCodeMapping", value);
|
||||||
/>
|
}}
|
||||||
|
className="nowheel nopan"
|
||||||
|
fontSize={8}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-52">
|
<Separator />
|
||||||
<Switch
|
<div className="flex items-center justify-between">
|
||||||
checked={inputs.continueOnFailure}
|
<div className="flex gap-2">
|
||||||
onCheckedChange={(checked) => {
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
if (!editable) {
|
Continue on Failure
|
||||||
return;
|
</Label>
|
||||||
}
|
<HelpTooltip
|
||||||
handleChange("continueOnFailure", checked);
|
content={helpTooltips["action"]["continueOnFailure"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-52">
|
||||||
|
<Switch
|
||||||
|
checked={inputs.continueOnFailure}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
if (!editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleChange("continueOnFailure", checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
Cache Actions
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["action"]["cacheActions"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-52">
|
||||||
|
<Switch
|
||||||
|
checked={inputs.cacheActions}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
if (!editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleChange("cacheActions", checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
Complete on Download
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["action"]["completeOnDownload"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-52">
|
||||||
|
<Switch
|
||||||
|
checked={inputs.allowDownloads}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
if (!editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleChange("allowDownloads", checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
File Suffix
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["action"]["fileSuffix"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<WorkflowBlockInput
|
||||||
|
nodeId={id}
|
||||||
|
type="text"
|
||||||
|
placeholder={placeholders["action"]["downloadSuffix"]}
|
||||||
|
className="nopan w-52 text-xs"
|
||||||
|
value={inputs.downloadSuffix ?? ""}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("downloadSuffix", value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<Separator />
|
||||||
<div className="flex items-center justify-between">
|
<div className="space-y-2">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
<Label className="text-xs text-slate-300">
|
||||||
Cache Actions
|
2FA Identifier
|
||||||
</Label>
|
</Label>
|
||||||
<HelpTooltip
|
<HelpTooltip
|
||||||
content={helpTooltips["action"]["cacheActions"]}
|
content={helpTooltips["action"]["totpIdentifier"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-52">
|
<WorkflowBlockInputTextarea
|
||||||
<Switch
|
nodeId={id}
|
||||||
checked={inputs.cacheActions}
|
onChange={(value) => {
|
||||||
onCheckedChange={(checked) => {
|
handleChange("totpIdentifier", value);
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("cacheActions", checked);
|
|
||||||
}}
|
}}
|
||||||
|
value={inputs.totpIdentifier ?? ""}
|
||||||
|
placeholder={placeholders["action"]["totpIdentifier"]}
|
||||||
|
className="nopan text-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="space-y-2">
|
||||||
<Separator />
|
<div className="flex gap-2">
|
||||||
<div className="flex items-center justify-between">
|
<Label className="text-xs text-slate-300">
|
||||||
<div className="flex gap-2">
|
2FA Verification URL
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
</Label>
|
||||||
Complete on Download
|
<HelpTooltip
|
||||||
</Label>
|
content={helpTooltips["task"]["totpVerificationUrl"]}
|
||||||
<HelpTooltip
|
/>
|
||||||
content={helpTooltips["action"]["completeOnDownload"]}
|
</div>
|
||||||
/>
|
<WorkflowBlockInputTextarea
|
||||||
</div>
|
nodeId={id}
|
||||||
<div className="w-52">
|
onChange={(value) => {
|
||||||
<Switch
|
handleChange("totpVerificationUrl", value);
|
||||||
checked={inputs.allowDownloads}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("allowDownloads", checked);
|
|
||||||
}}
|
}}
|
||||||
|
value={inputs.totpVerificationUrl ?? ""}
|
||||||
|
placeholder={placeholders["task"]["totpVerificationUrl"]}
|
||||||
|
className="nopan text-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
</AccordionContent>
|
||||||
<div className="flex gap-2">
|
</AccordionItem>
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
</Accordion>
|
||||||
File Suffix
|
</div>
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["action"]["fileSuffix"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<WorkflowBlockInput
|
|
||||||
nodeId={id}
|
|
||||||
type="text"
|
|
||||||
placeholder={placeholders["action"]["downloadSuffix"]}
|
|
||||||
className="nopan w-52 text-xs"
|
|
||||||
value={inputs.downloadSuffix ?? ""}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("downloadSuffix", value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs text-slate-300">
|
|
||||||
2FA Identifier
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["action"]["totpIdentifier"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<WorkflowBlockInputTextarea
|
|
||||||
nodeId={id}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("totpIdentifier", value);
|
|
||||||
}}
|
|
||||||
value={inputs.totpIdentifier ?? ""}
|
|
||||||
placeholder={placeholders["action"]["totpIdentifier"]}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs text-slate-300">
|
|
||||||
2FA Verification URL
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["task"]["totpVerificationUrl"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<WorkflowBlockInputTextarea
|
|
||||||
nodeId={id}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("totpVerificationUrl", value);
|
|
||||||
}}
|
|
||||||
value={inputs.totpVerificationUrl ?? ""}
|
|
||||||
placeholder={placeholders["task"]["totpVerificationUrl"]}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<BlockCodeEditor blockLabel={label} blockType={type} script={script} />
|
||||||
|
</Flippable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Flippable } from "@/components/Flippable";
|
||||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
@@ -22,6 +24,7 @@ import { dataSchemaExampleValue } from "../types";
|
|||||||
import type { ExtractionNode } from "./types";
|
import type { ExtractionNode } from "./types";
|
||||||
|
|
||||||
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
||||||
|
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
|
||||||
import { helpTooltips, placeholders } from "../../helpContent";
|
import { helpTooltips, placeholders } from "../../helpContent";
|
||||||
import { AppNode } from "..";
|
import { AppNode } from "..";
|
||||||
import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils";
|
import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils";
|
||||||
@@ -31,13 +34,17 @@ import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow"
|
|||||||
import { RunEngineSelector } from "@/components/EngineSelector";
|
import { RunEngineSelector } from "@/components/EngineSelector";
|
||||||
import { ModelSelector } from "@/components/ModelSelector";
|
import { ModelSelector } from "@/components/ModelSelector";
|
||||||
import { useDebugStore } from "@/store/useDebugStore";
|
import { useDebugStore } from "@/store/useDebugStore";
|
||||||
|
import { useBlockScriptStore } from "@/store/BlockScriptStore";
|
||||||
import { cn } from "@/util/utils";
|
import { cn } from "@/util/utils";
|
||||||
import { NodeHeader } from "../components/NodeHeader";
|
import { NodeHeader } from "../components/NodeHeader";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
function ExtractionNode({ id, data, type }: NodeProps<ExtractionNode>) {
|
function ExtractionNode({ id, data, type }: NodeProps<ExtractionNode>) {
|
||||||
const { updateNodeData } = useReactFlow();
|
const { updateNodeData } = useReactFlow();
|
||||||
|
const [facing, setFacing] = useState<"front" | "back">("front");
|
||||||
|
const blockScriptStore = useBlockScriptStore();
|
||||||
const { debuggable, editable, label } = data;
|
const { debuggable, editable, label } = data;
|
||||||
|
const script = blockScriptStore.scripts[label];
|
||||||
const debugStore = useDebugStore();
|
const debugStore = useDebugStore();
|
||||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||||
const { blockLabel: urlBlockLabel } = useParams();
|
const { blockLabel: urlBlockLabel } = useParams();
|
||||||
@@ -67,193 +74,204 @@ function ExtractionNode({ id, data, type }: NodeProps<ExtractionNode>) {
|
|||||||
updateNodeData(id, { [key]: value });
|
updateNodeData(id, { [key]: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<div>
|
setFacing(data.showCode ? "back" : "front");
|
||||||
<Handle
|
}, [data.showCode]);
|
||||||
type="source"
|
|
||||||
position={Position.Bottom}
|
|
||||||
id="a"
|
|
||||||
className="opacity-0"
|
|
||||||
/>
|
|
||||||
<Handle
|
|
||||||
type="target"
|
|
||||||
position={Position.Top}
|
|
||||||
id="b"
|
|
||||||
className="opacity-0"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
|
||||||
{
|
|
||||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
|
||||||
thisBlockIsPlaying,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<NodeHeader
|
|
||||||
blockLabel={label}
|
|
||||||
disabled={elideFromDebugging}
|
|
||||||
editable={editable}
|
|
||||||
nodeId={id}
|
|
||||||
totpIdentifier={null}
|
|
||||||
totpUrl={null}
|
|
||||||
type={type}
|
|
||||||
/>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs text-slate-300">
|
|
||||||
Data Extraction Goal
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["extraction"]["dataExtractionGoal"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{isFirstWorkflowBlock ? (
|
|
||||||
<div className="flex justify-end text-xs text-slate-400">
|
|
||||||
Tip: Use the {"+"} button to add parameters!
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<WorkflowBlockInputTextarea
|
return (
|
||||||
nodeId={id}
|
<Flippable facing={facing} preserveFrontsideHeight={true}>
|
||||||
onChange={(value) => {
|
<div>
|
||||||
if (!editable) {
|
<Handle
|
||||||
return;
|
type="source"
|
||||||
}
|
position={Position.Bottom}
|
||||||
handleChange("dataExtractionGoal", value);
|
id="a"
|
||||||
}}
|
className="opacity-0"
|
||||||
value={inputs.dataExtractionGoal}
|
|
||||||
placeholder={placeholders["extraction"]["dataExtractionGoal"]}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<WorkflowDataSchemaInputGroup
|
|
||||||
value={inputs.dataSchema}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("dataSchema", value);
|
|
||||||
}}
|
|
||||||
exampleValue={dataSchemaExampleValue}
|
|
||||||
suggestionContext={{
|
|
||||||
data_extraction_goal: inputs.dataExtractionGoal,
|
|
||||||
current_schema: inputs.dataSchema,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Separator />
|
<Handle
|
||||||
<Accordion type="single" collapsible>
|
type="target"
|
||||||
<AccordionItem value="advanced" className="border-b-0">
|
position={Position.Top}
|
||||||
<AccordionTrigger className="py-0">
|
id="b"
|
||||||
Advanced Settings
|
className="opacity-0"
|
||||||
</AccordionTrigger>
|
/>
|
||||||
<AccordionContent className="pl-6 pr-1 pt-1">
|
<div
|
||||||
<div className="space-y-4">
|
className={cn(
|
||||||
<div className="space-y-2">
|
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||||
<ModelSelector
|
{
|
||||||
className="nopan w-52 text-xs"
|
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||||
value={inputs.model}
|
thisBlockIsPlaying,
|
||||||
onChange={(value) => {
|
},
|
||||||
handleChange("model", value);
|
)}
|
||||||
}}
|
>
|
||||||
/>
|
<NodeHeader
|
||||||
<ParametersMultiSelect
|
blockLabel={label}
|
||||||
availableOutputParameters={outputParameterKeys}
|
disabled={elideFromDebugging}
|
||||||
parameters={data.parameterKeys}
|
editable={editable}
|
||||||
onParametersChange={(parameterKeys) => {
|
nodeId={id}
|
||||||
updateNodeData(id, { parameterKeys });
|
totpIdentifier={null}
|
||||||
}}
|
totpUrl={null}
|
||||||
/>
|
type={type}
|
||||||
</div>
|
/>
|
||||||
<div className="flex items-center justify-between">
|
<div className="space-y-2">
|
||||||
<div className="flex gap-2">
|
<div className="flex justify-between">
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
<div className="flex gap-2">
|
||||||
Engine
|
<Label className="text-xs text-slate-300">
|
||||||
</Label>
|
Data Extraction Goal
|
||||||
</div>
|
</Label>
|
||||||
<RunEngineSelector
|
<HelpTooltip
|
||||||
value={inputs.engine}
|
content={helpTooltips["extraction"]["dataExtractionGoal"]}
|
||||||
onChange={(value) => {
|
/>
|
||||||
handleChange("engine", value);
|
|
||||||
}}
|
|
||||||
className="nopan w-52 text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
|
||||||
Max Steps Override
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["extraction"]["maxStepsOverride"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
placeholder={placeholders["extraction"]["maxStepsOverride"]}
|
|
||||||
className="nopan w-52 text-xs"
|
|
||||||
min="0"
|
|
||||||
value={inputs.maxStepsOverride ?? ""}
|
|
||||||
onChange={(event) => {
|
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const value =
|
|
||||||
event.target.value === ""
|
|
||||||
? null
|
|
||||||
: Number(event.target.value);
|
|
||||||
handleChange("maxStepsOverride", value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
|
||||||
Continue on Failure
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["extraction"]["continueOnFailure"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-52">
|
|
||||||
<Switch
|
|
||||||
checked={inputs.continueOnFailure}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("continueOnFailure", checked);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
|
||||||
Cache Actions
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["extraction"]["cacheActions"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-52">
|
|
||||||
<Switch
|
|
||||||
checked={inputs.cacheActions}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
if (!editable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleChange("cacheActions", checked);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</AccordionContent>
|
{isFirstWorkflowBlock ? (
|
||||||
</AccordionItem>
|
<div className="flex justify-end text-xs text-slate-400">
|
||||||
</Accordion>
|
Tip: Use the {"+"} button to add parameters!
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<WorkflowBlockInputTextarea
|
||||||
|
nodeId={id}
|
||||||
|
onChange={(value) => {
|
||||||
|
if (!editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleChange("dataExtractionGoal", value);
|
||||||
|
}}
|
||||||
|
value={inputs.dataExtractionGoal}
|
||||||
|
placeholder={placeholders["extraction"]["dataExtractionGoal"]}
|
||||||
|
className="nopan text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<WorkflowDataSchemaInputGroup
|
||||||
|
value={inputs.dataSchema}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("dataSchema", value);
|
||||||
|
}}
|
||||||
|
exampleValue={dataSchemaExampleValue}
|
||||||
|
suggestionContext={{
|
||||||
|
data_extraction_goal: inputs.dataExtractionGoal,
|
||||||
|
current_schema: inputs.dataSchema,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
<Accordion type="single" collapsible>
|
||||||
|
<AccordionItem value="advanced" className="border-b-0">
|
||||||
|
<AccordionTrigger className="py-0">
|
||||||
|
Advanced Settings
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="pl-6 pr-1 pt-1">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<ModelSelector
|
||||||
|
className="nopan w-52 text-xs"
|
||||||
|
value={inputs.model}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("model", value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ParametersMultiSelect
|
||||||
|
availableOutputParameters={outputParameterKeys}
|
||||||
|
parameters={data.parameterKeys}
|
||||||
|
onParametersChange={(parameterKeys) => {
|
||||||
|
updateNodeData(id, { parameterKeys });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
Engine
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<RunEngineSelector
|
||||||
|
value={inputs.engine}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("engine", value);
|
||||||
|
}}
|
||||||
|
className="nopan w-52 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
Max Steps Override
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["extraction"]["maxStepsOverride"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder={
|
||||||
|
placeholders["extraction"]["maxStepsOverride"]
|
||||||
|
}
|
||||||
|
className="nopan w-52 text-xs"
|
||||||
|
min="0"
|
||||||
|
value={inputs.maxStepsOverride ?? ""}
|
||||||
|
onChange={(event) => {
|
||||||
|
if (!editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const value =
|
||||||
|
event.target.value === ""
|
||||||
|
? null
|
||||||
|
: Number(event.target.value);
|
||||||
|
handleChange("maxStepsOverride", value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
Continue on Failure
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={
|
||||||
|
helpTooltips["extraction"]["continueOnFailure"]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-52">
|
||||||
|
<Switch
|
||||||
|
checked={inputs.continueOnFailure}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
if (!editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleChange("continueOnFailure", checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
Cache Actions
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["extraction"]["cacheActions"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-52">
|
||||||
|
<Switch
|
||||||
|
checked={inputs.cacheActions}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
if (!editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleChange("cacheActions", checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<BlockCodeEditor blockLabel={label} blockType={type} script={script} />
|
||||||
|
</Flippable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Flippable } from "@/components/Flippable";
|
||||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
@@ -11,7 +13,9 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
||||||
|
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
|
||||||
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
||||||
|
import { useBlockScriptStore } from "@/store/BlockScriptStore";
|
||||||
import {
|
import {
|
||||||
Handle,
|
Handle,
|
||||||
NodeProps,
|
NodeProps,
|
||||||
@@ -44,7 +48,10 @@ const navigationGoalPlaceholder = "Tell Skyvern which file to download.";
|
|||||||
|
|
||||||
function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||||
const { updateNodeData } = useReactFlow();
|
const { updateNodeData } = useReactFlow();
|
||||||
|
const [facing, setFacing] = useState<"front" | "back">("front");
|
||||||
|
const blockScriptStore = useBlockScriptStore();
|
||||||
const { debuggable, editable, label } = data;
|
const { debuggable, editable, label } = data;
|
||||||
|
const script = blockScriptStore.scripts[label];
|
||||||
const debugStore = useDebugStore();
|
const debugStore = useDebugStore();
|
||||||
const { blockLabel: urlBlockLabel } = useParams();
|
const { blockLabel: urlBlockLabel } = useParams();
|
||||||
const thisBlockIsPlaying =
|
const thisBlockIsPlaying =
|
||||||
@@ -75,282 +82,297 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
|||||||
updateNodeData(id, { [key]: value });
|
updateNodeData(id, { [key]: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFacing(data.showCode ? "back" : "front");
|
||||||
|
}, [data.showCode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Flippable facing={facing} preserveFrontsideHeight={true}>
|
||||||
<Handle
|
<div>
|
||||||
type="source"
|
<Handle
|
||||||
position={Position.Bottom}
|
type="source"
|
||||||
id="a"
|
position={Position.Bottom}
|
||||||
className="opacity-0"
|
id="a"
|
||||||
/>
|
className="opacity-0"
|
||||||
<Handle
|
|
||||||
type="target"
|
|
||||||
position={Position.Top}
|
|
||||||
id="b"
|
|
||||||
className="opacity-0"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
|
||||||
{
|
|
||||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
|
||||||
thisBlockIsPlaying,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<NodeHeader
|
|
||||||
blockLabel={label}
|
|
||||||
disabled={elideFromDebugging}
|
|
||||||
editable={editable}
|
|
||||||
nodeId={id}
|
|
||||||
totpIdentifier={inputs.totpIdentifier}
|
|
||||||
totpUrl={inputs.totpVerificationUrl}
|
|
||||||
type="file_download" // sic: the naming for this block is not consistent
|
|
||||||
/>
|
/>
|
||||||
<div className="space-y-4">
|
<Handle
|
||||||
<div className="space-y-2">
|
type="target"
|
||||||
<div className="flex justify-between">
|
position={Position.Top}
|
||||||
<div className="flex gap-2">
|
id="b"
|
||||||
<Label className="text-xs text-slate-300">URL</Label>
|
className="opacity-0"
|
||||||
<HelpTooltip content={urlTooltip} />
|
/>
|
||||||
</div>
|
<div
|
||||||
{isFirstWorkflowBlock ? (
|
className={cn(
|
||||||
<div className="flex justify-end text-xs text-slate-400">
|
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||||
Tip: Use the {"+"} button to add parameters!
|
{
|
||||||
|
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||||
|
thisBlockIsPlaying,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<NodeHeader
|
||||||
|
blockLabel={label}
|
||||||
|
disabled={elideFromDebugging}
|
||||||
|
editable={editable}
|
||||||
|
nodeId={id}
|
||||||
|
totpIdentifier={inputs.totpIdentifier}
|
||||||
|
totpUrl={inputs.totpVerificationUrl}
|
||||||
|
type="file_download" // sic: the naming for this block is not consistent
|
||||||
|
/>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs text-slate-300">URL</Label>
|
||||||
|
<HelpTooltip content={urlTooltip} />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
{isFirstWorkflowBlock ? (
|
||||||
</div>
|
<div className="flex justify-end text-xs text-slate-400">
|
||||||
<WorkflowBlockInputTextarea
|
Tip: Use the {"+"} button to add parameters!
|
||||||
canWriteTitle={true}
|
|
||||||
nodeId={id}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("url", value);
|
|
||||||
}}
|
|
||||||
value={inputs.url}
|
|
||||||
placeholder={urlPlaceholder}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs text-slate-300">Download Goal</Label>
|
|
||||||
<HelpTooltip content={navigationGoalTooltip} />
|
|
||||||
</div>
|
|
||||||
<WorkflowBlockInputTextarea
|
|
||||||
nodeId={id}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("navigationGoal", value);
|
|
||||||
}}
|
|
||||||
value={inputs.navigationGoal}
|
|
||||||
placeholder={navigationGoalPlaceholder}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-md bg-slate-800 p-2 text-xs text-slate-400">
|
|
||||||
Once the file is downloaded, this block will complete.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
|
||||||
<Accordion type="single" collapsible>
|
|
||||||
<AccordionItem value="advanced" className="border-b-0">
|
|
||||||
<AccordionTrigger className="py-0">
|
|
||||||
Advanced Settings
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="pl-6 pr-1 pt-1">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<ModelSelector
|
|
||||||
className="nopan w-52 text-xs"
|
|
||||||
value={inputs.model}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("model", value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ParametersMultiSelect
|
|
||||||
availableOutputParameters={outputParameterKeys}
|
|
||||||
parameters={data.parameterKeys}
|
|
||||||
onParametersChange={(parameterKeys) => {
|
|
||||||
updateNodeData(id, { parameterKeys });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
|
||||||
Engine
|
|
||||||
</Label>
|
|
||||||
</div>
|
</div>
|
||||||
<RunEngineSelector
|
) : null}
|
||||||
value={inputs.engine}
|
</div>
|
||||||
onChange={(value) => {
|
<WorkflowBlockInputTextarea
|
||||||
handleChange("engine", value);
|
canWriteTitle={true}
|
||||||
}}
|
nodeId={id}
|
||||||
className="nopan w-52 text-xs"
|
onChange={(value) => {
|
||||||
/>
|
handleChange("url", value);
|
||||||
</div>
|
}}
|
||||||
<div className="flex items-center justify-between">
|
value={inputs.url}
|
||||||
<div className="flex gap-2">
|
placeholder={urlPlaceholder}
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
className="nopan text-xs"
|
||||||
Max Steps Override
|
/>
|
||||||
</Label>
|
</div>
|
||||||
<HelpTooltip
|
<div className="space-y-2">
|
||||||
content={helpTooltips["download"]["maxStepsOverride"]}
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs text-slate-300">Download Goal</Label>
|
||||||
|
<HelpTooltip content={navigationGoalTooltip} />
|
||||||
|
</div>
|
||||||
|
<WorkflowBlockInputTextarea
|
||||||
|
nodeId={id}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("navigationGoal", value);
|
||||||
|
}}
|
||||||
|
value={inputs.navigationGoal}
|
||||||
|
placeholder={navigationGoalPlaceholder}
|
||||||
|
className="nopan text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-md bg-slate-800 p-2 text-xs text-slate-400">
|
||||||
|
Once the file is downloaded, this block will complete.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<Accordion type="single" collapsible>
|
||||||
|
<AccordionItem value="advanced" className="border-b-0">
|
||||||
|
<AccordionTrigger className="py-0">
|
||||||
|
Advanced Settings
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="pl-6 pr-1 pt-1">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<ModelSelector
|
||||||
|
className="nopan w-52 text-xs"
|
||||||
|
value={inputs.model}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("model", value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ParametersMultiSelect
|
||||||
|
availableOutputParameters={outputParameterKeys}
|
||||||
|
parameters={data.parameterKeys}
|
||||||
|
onParametersChange={(parameterKeys) => {
|
||||||
|
updateNodeData(id, { parameterKeys });
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<div className="flex items-center justify-between">
|
||||||
type="number"
|
|
||||||
placeholder={placeholders["download"]["maxStepsOverride"]}
|
|
||||||
className="nopan w-52 text-xs"
|
|
||||||
min="0"
|
|
||||||
value={inputs.maxStepsOverride ?? ""}
|
|
||||||
onChange={(event) => {
|
|
||||||
const value =
|
|
||||||
event.target.value === ""
|
|
||||||
? null
|
|
||||||
: Number(event.target.value);
|
|
||||||
handleChange("maxStepsOverride", value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex gap-4">
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
Error Messages
|
Engine
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<RunEngineSelector
|
||||||
|
value={inputs.engine}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("engine", value);
|
||||||
|
}}
|
||||||
|
className="nopan w-52 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
Max Steps Override
|
||||||
</Label>
|
</Label>
|
||||||
<HelpTooltip
|
<HelpTooltip
|
||||||
content={helpTooltips["download"]["errorCodeMapping"]}
|
content={helpTooltips["download"]["maxStepsOverride"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Checkbox
|
<Input
|
||||||
checked={inputs.errorCodeMapping !== "null"}
|
type="number"
|
||||||
disabled={!editable}
|
placeholder={placeholders["download"]["maxStepsOverride"]}
|
||||||
onCheckedChange={(checked) => {
|
className="nopan w-52 text-xs"
|
||||||
handleChange(
|
min="0"
|
||||||
"errorCodeMapping",
|
value={inputs.maxStepsOverride ?? ""}
|
||||||
checked
|
onChange={(event) => {
|
||||||
? JSON.stringify(errorMappingExampleValue, null, 2)
|
const value =
|
||||||
: "null",
|
event.target.value === ""
|
||||||
);
|
? null
|
||||||
|
: Number(event.target.value);
|
||||||
|
handleChange("maxStepsOverride", value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{inputs.errorCodeMapping !== "null" && (
|
<div className="space-y-2">
|
||||||
<div>
|
<div className="flex gap-4">
|
||||||
<CodeEditor
|
<div className="flex gap-2">
|
||||||
language="json"
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
value={inputs.errorCodeMapping}
|
Error Messages
|
||||||
onChange={(value) => {
|
</Label>
|
||||||
handleChange("errorCodeMapping", value);
|
<HelpTooltip
|
||||||
|
content={helpTooltips["download"]["errorCodeMapping"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
checked={inputs.errorCodeMapping !== "null"}
|
||||||
|
disabled={!editable}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
handleChange(
|
||||||
|
"errorCodeMapping",
|
||||||
|
checked
|
||||||
|
? JSON.stringify(
|
||||||
|
errorMappingExampleValue,
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
: "null",
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
className="nowheel nopan"
|
|
||||||
fontSize={8}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
{inputs.errorCodeMapping !== "null" && (
|
||||||
</div>
|
<div>
|
||||||
<Separator />
|
<CodeEditor
|
||||||
<div className="flex items-center justify-between">
|
language="json"
|
||||||
<div className="flex gap-2">
|
value={inputs.errorCodeMapping}
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
onChange={(value) => {
|
||||||
Continue on Failure
|
handleChange("errorCodeMapping", value);
|
||||||
</Label>
|
}}
|
||||||
<HelpTooltip
|
className="nowheel nopan"
|
||||||
content={helpTooltips["download"]["continueOnFailure"]}
|
fontSize={8}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-52">
|
<Separator />
|
||||||
<Switch
|
<div className="flex items-center justify-between">
|
||||||
checked={inputs.continueOnFailure}
|
<div className="flex gap-2">
|
||||||
onCheckedChange={(checked) => {
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
handleChange("continueOnFailure", checked);
|
Continue on Failure
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["download"]["continueOnFailure"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-52">
|
||||||
|
<Switch
|
||||||
|
checked={inputs.continueOnFailure}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
handleChange("continueOnFailure", checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
Cache Actions
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["download"]["cacheActions"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-52">
|
||||||
|
<Switch
|
||||||
|
checked={inputs.cacheActions}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
handleChange("cacheActions", checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
File Suffix
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["download"]["fileSuffix"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder={placeholders["download"]["downloadSuffix"]}
|
||||||
|
className="nopan w-52 text-xs"
|
||||||
|
value={inputs.downloadSuffix ?? ""}
|
||||||
|
onChange={(event) => {
|
||||||
|
handleChange("downloadSuffix", event.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<Separator />
|
||||||
<div className="flex items-center justify-between">
|
<div className="space-y-2">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
<Label className="text-xs text-slate-300">
|
||||||
Cache Actions
|
2FA Identifier
|
||||||
</Label>
|
</Label>
|
||||||
<HelpTooltip
|
<HelpTooltip
|
||||||
content={helpTooltips["download"]["cacheActions"]}
|
content={helpTooltips["download"]["totpIdentifier"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-52">
|
<WorkflowBlockInputTextarea
|
||||||
<Switch
|
nodeId={id}
|
||||||
checked={inputs.cacheActions}
|
onChange={(value) => {
|
||||||
onCheckedChange={(checked) => {
|
handleChange("totpIdentifier", value);
|
||||||
handleChange("cacheActions", checked);
|
|
||||||
}}
|
}}
|
||||||
|
value={inputs.totpIdentifier ?? ""}
|
||||||
|
placeholder={placeholders["download"]["totpIdentifier"]}
|
||||||
|
className="nopan text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs text-slate-300">
|
||||||
|
2FA Verification URL
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["task"]["totpVerificationUrl"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<WorkflowBlockInputTextarea
|
||||||
|
nodeId={id}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("totpVerificationUrl", value);
|
||||||
|
}}
|
||||||
|
value={inputs.totpVerificationUrl ?? ""}
|
||||||
|
placeholder={placeholders["task"]["totpVerificationUrl"]}
|
||||||
|
className="nopan text-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
</AccordionContent>
|
||||||
<div className="flex items-center justify-between">
|
</AccordionItem>
|
||||||
<div className="flex gap-2">
|
</Accordion>
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
</div>
|
||||||
File Suffix
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["download"]["fileSuffix"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
placeholder={placeholders["download"]["downloadSuffix"]}
|
|
||||||
className="nopan w-52 text-xs"
|
|
||||||
value={inputs.downloadSuffix ?? ""}
|
|
||||||
onChange={(event) => {
|
|
||||||
handleChange("downloadSuffix", event.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs text-slate-300">
|
|
||||||
2FA Identifier
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["download"]["totpIdentifier"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<WorkflowBlockInputTextarea
|
|
||||||
nodeId={id}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("totpIdentifier", value);
|
|
||||||
}}
|
|
||||||
value={inputs.totpIdentifier ?? ""}
|
|
||||||
placeholder={placeholders["download"]["totpIdentifier"]}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs text-slate-300">
|
|
||||||
2FA Verification URL
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["task"]["totpVerificationUrl"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<WorkflowBlockInputTextarea
|
|
||||||
nodeId={id}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("totpVerificationUrl", value);
|
|
||||||
}}
|
|
||||||
value={inputs.totpVerificationUrl ?? ""}
|
|
||||||
placeholder={placeholders["task"]["totpVerificationUrl"]}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<BlockCodeEditor
|
||||||
|
blockLabel={label}
|
||||||
|
blockType="file_download" // sic: naming is not consistent
|
||||||
|
script={script}
|
||||||
|
/>
|
||||||
|
</Flippable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Flippable } from "@/components/Flippable";
|
||||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
@@ -11,7 +13,9 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
||||||
|
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
|
||||||
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
||||||
|
import { useBlockScriptStore } from "@/store/BlockScriptStore";
|
||||||
import {
|
import {
|
||||||
Handle,
|
Handle,
|
||||||
NodeProps,
|
NodeProps,
|
||||||
@@ -38,7 +42,10 @@ import { useParams } from "react-router-dom";
|
|||||||
|
|
||||||
function LoginNode({ id, data, type }: NodeProps<LoginNode>) {
|
function LoginNode({ id, data, type }: NodeProps<LoginNode>) {
|
||||||
const { updateNodeData } = useReactFlow();
|
const { updateNodeData } = useReactFlow();
|
||||||
|
const [facing, setFacing] = useState<"front" | "back">("front");
|
||||||
|
const blockScriptStore = useBlockScriptStore();
|
||||||
const { debuggable, editable, label } = data;
|
const { debuggable, editable, label } = data;
|
||||||
|
const script = blockScriptStore.scripts[label];
|
||||||
const debugStore = useDebugStore();
|
const debugStore = useDebugStore();
|
||||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||||
const { blockLabel: urlBlockLabel } = useParams();
|
const { blockLabel: urlBlockLabel } = useParams();
|
||||||
@@ -72,291 +79,304 @@ function LoginNode({ id, data, type }: NodeProps<LoginNode>) {
|
|||||||
updateNodeData(id, { [key]: value });
|
updateNodeData(id, { [key]: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<div>
|
setFacing(data.showCode ? "back" : "front");
|
||||||
<Handle
|
}, [data.showCode]);
|
||||||
type="source"
|
|
||||||
position={Position.Bottom}
|
|
||||||
id="a"
|
|
||||||
className="opacity-0"
|
|
||||||
/>
|
|
||||||
<Handle
|
|
||||||
type="target"
|
|
||||||
position={Position.Top}
|
|
||||||
id="b"
|
|
||||||
className="opacity-0"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
|
||||||
{
|
|
||||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
|
||||||
thisBlockIsPlaying,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<NodeHeader
|
|
||||||
blockLabel={label}
|
|
||||||
disabled={elideFromDebugging}
|
|
||||||
editable={editable}
|
|
||||||
nodeId={id}
|
|
||||||
totpIdentifier={inputs.totpIdentifier}
|
|
||||||
totpUrl={inputs.totpVerificationUrl}
|
|
||||||
type={type}
|
|
||||||
/>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs text-slate-300">URL</Label>
|
|
||||||
<HelpTooltip content={helpTooltips["login"]["url"]} />
|
|
||||||
</div>
|
|
||||||
{isFirstWorkflowBlock ? (
|
|
||||||
<div className="flex justify-end text-xs text-slate-400">
|
|
||||||
Tip: Use the {"+"} button to add parameters!
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<WorkflowBlockInputTextarea
|
return (
|
||||||
canWriteTitle={true}
|
<Flippable facing={facing} preserveFrontsideHeight={true}>
|
||||||
nodeId={id}
|
<div>
|
||||||
onChange={(value) => {
|
<Handle
|
||||||
handleChange("url", value);
|
type="source"
|
||||||
}}
|
position={Position.Bottom}
|
||||||
value={inputs.url}
|
id="a"
|
||||||
placeholder={placeholders["login"]["url"]}
|
className="opacity-0"
|
||||||
className="nopan text-xs"
|
/>
|
||||||
/>
|
<Handle
|
||||||
</div>
|
type="target"
|
||||||
<div className="space-y-2">
|
position={Position.Top}
|
||||||
<div className="flex gap-2">
|
id="b"
|
||||||
<Label className="text-xs text-slate-300">Login Goal</Label>
|
className="opacity-0"
|
||||||
<HelpTooltip content={helpTooltips["login"]["navigationGoal"]} />
|
/>
|
||||||
</div>
|
<div
|
||||||
<WorkflowBlockInputTextarea
|
className={cn(
|
||||||
nodeId={id}
|
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||||
onChange={(value) => {
|
{
|
||||||
handleChange("navigationGoal", value);
|
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||||
}}
|
thisBlockIsPlaying,
|
||||||
value={inputs.navigationGoal}
|
},
|
||||||
placeholder={placeholders["login"]["navigationGoal"]}
|
)}
|
||||||
className="nopan text-xs"
|
>
|
||||||
/>
|
<NodeHeader
|
||||||
</div>
|
blockLabel={label}
|
||||||
<div className="space-y-2">
|
disabled={elideFromDebugging}
|
||||||
<Label className="text-xs text-slate-300">Credential</Label>
|
editable={editable}
|
||||||
<LoginBlockCredentialSelector
|
nodeId={id}
|
||||||
nodeId={id}
|
totpIdentifier={inputs.totpIdentifier}
|
||||||
value={
|
totpUrl={inputs.totpVerificationUrl}
|
||||||
data.parameterKeys.length > 0
|
type={type}
|
||||||
? data.parameterKeys[0]
|
/>
|
||||||
: undefined
|
<div className="space-y-4">
|
||||||
}
|
<div className="space-y-2">
|
||||||
onChange={(value) => {
|
<div className="flex justify-between">
|
||||||
if (!editable) {
|
<div className="flex gap-2">
|
||||||
return;
|
<Label className="text-xs text-slate-300">URL</Label>
|
||||||
}
|
<HelpTooltip content={helpTooltips["login"]["url"]} />
|
||||||
updateNodeData(id, { parameterKeys: [value] });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
|
||||||
<Accordion type="single" collapsible>
|
|
||||||
<AccordionItem value="advanced" className="border-b-0">
|
|
||||||
<AccordionTrigger className="py-0">
|
|
||||||
Advanced Settings
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="pl-6 pr-1 pt-1">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<ModelSelector
|
|
||||||
className="nopan w-52 text-xs"
|
|
||||||
value={inputs.model}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("model", value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ParametersMultiSelect
|
|
||||||
availableOutputParameters={outputParameterKeys}
|
|
||||||
parameters={data.parameterKeys}
|
|
||||||
onParametersChange={(parameterKeys) => {
|
|
||||||
updateNodeData(id, { parameterKeys });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
{isFirstWorkflowBlock ? (
|
||||||
<Label className="text-xs text-slate-300">
|
<div className="flex justify-end text-xs text-slate-400">
|
||||||
Complete if...
|
Tip: Use the {"+"} button to add parameters!
|
||||||
</Label>
|
|
||||||
<WorkflowBlockInputTextarea
|
|
||||||
nodeId={id}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("completeCriterion", value);
|
|
||||||
}}
|
|
||||||
value={inputs.completeCriterion}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
|
||||||
Engine
|
|
||||||
</Label>
|
|
||||||
</div>
|
</div>
|
||||||
<RunEngineSelector
|
) : null}
|
||||||
value={inputs.engine}
|
</div>
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("engine", value);
|
<WorkflowBlockInputTextarea
|
||||||
}}
|
canWriteTitle={true}
|
||||||
className="nopan w-52 text-xs"
|
nodeId={id}
|
||||||
/>
|
onChange={(value) => {
|
||||||
</div>
|
handleChange("url", value);
|
||||||
<div className="flex items-center justify-between">
|
}}
|
||||||
<div className="flex gap-2">
|
value={inputs.url}
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
placeholder={placeholders["login"]["url"]}
|
||||||
Max Steps Override
|
className="nopan text-xs"
|
||||||
</Label>
|
/>
|
||||||
<HelpTooltip
|
</div>
|
||||||
content={helpTooltips["login"]["maxStepsOverride"]}
|
<div className="space-y-2">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs text-slate-300">Login Goal</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["login"]["navigationGoal"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<WorkflowBlockInputTextarea
|
||||||
|
nodeId={id}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("navigationGoal", value);
|
||||||
|
}}
|
||||||
|
value={inputs.navigationGoal}
|
||||||
|
placeholder={placeholders["login"]["navigationGoal"]}
|
||||||
|
className="nopan text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="text-xs text-slate-300">Credential</Label>
|
||||||
|
<LoginBlockCredentialSelector
|
||||||
|
nodeId={id}
|
||||||
|
value={
|
||||||
|
data.parameterKeys.length > 0
|
||||||
|
? data.parameterKeys[0]
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onChange={(value) => {
|
||||||
|
if (!editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateNodeData(id, { parameterKeys: [value] });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<Accordion type="single" collapsible>
|
||||||
|
<AccordionItem value="advanced" className="border-b-0">
|
||||||
|
<AccordionTrigger className="py-0">
|
||||||
|
Advanced Settings
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="pl-6 pr-1 pt-1">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<ModelSelector
|
||||||
|
className="nopan w-52 text-xs"
|
||||||
|
value={inputs.model}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("model", value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ParametersMultiSelect
|
||||||
|
availableOutputParameters={outputParameterKeys}
|
||||||
|
parameters={data.parameterKeys}
|
||||||
|
onParametersChange={(parameterKeys) => {
|
||||||
|
updateNodeData(id, { parameterKeys });
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<div className="space-y-2">
|
||||||
type="number"
|
<Label className="text-xs text-slate-300">
|
||||||
placeholder={placeholders["login"]["maxStepsOverride"]}
|
Complete if...
|
||||||
className="nopan w-52 text-xs"
|
</Label>
|
||||||
min="0"
|
<WorkflowBlockInputTextarea
|
||||||
value={inputs.maxStepsOverride ?? ""}
|
nodeId={id}
|
||||||
onChange={(event) => {
|
onChange={(value) => {
|
||||||
const value =
|
handleChange("completeCriterion", value);
|
||||||
event.target.value === ""
|
}}
|
||||||
? null
|
value={inputs.completeCriterion}
|
||||||
: Number(event.target.value);
|
className="nopan text-xs"
|
||||||
handleChange("maxStepsOverride", value);
|
/>
|
||||||
}}
|
</div>
|
||||||
/>
|
<Separator />
|
||||||
</div>
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex gap-4">
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
Error Messages
|
Engine
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<RunEngineSelector
|
||||||
|
value={inputs.engine}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("engine", value);
|
||||||
|
}}
|
||||||
|
className="nopan w-52 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
Max Steps Override
|
||||||
</Label>
|
</Label>
|
||||||
<HelpTooltip
|
<HelpTooltip
|
||||||
content={helpTooltips["login"]["errorCodeMapping"]}
|
content={helpTooltips["login"]["maxStepsOverride"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Checkbox
|
<Input
|
||||||
checked={inputs.errorCodeMapping !== "null"}
|
type="number"
|
||||||
disabled={!editable}
|
placeholder={placeholders["login"]["maxStepsOverride"]}
|
||||||
onCheckedChange={(checked) => {
|
className="nopan w-52 text-xs"
|
||||||
handleChange(
|
min="0"
|
||||||
"errorCodeMapping",
|
value={inputs.maxStepsOverride ?? ""}
|
||||||
checked
|
onChange={(event) => {
|
||||||
? JSON.stringify(errorMappingExampleValue, null, 2)
|
const value =
|
||||||
: "null",
|
event.target.value === ""
|
||||||
);
|
? null
|
||||||
|
: Number(event.target.value);
|
||||||
|
handleChange("maxStepsOverride", value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{inputs.errorCodeMapping !== "null" && (
|
<div className="space-y-2">
|
||||||
<div>
|
<div className="flex gap-4">
|
||||||
<CodeEditor
|
<div className="flex gap-2">
|
||||||
language="json"
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
value={inputs.errorCodeMapping}
|
Error Messages
|
||||||
onChange={(value) => {
|
</Label>
|
||||||
handleChange("errorCodeMapping", value);
|
<HelpTooltip
|
||||||
|
content={helpTooltips["login"]["errorCodeMapping"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
checked={inputs.errorCodeMapping !== "null"}
|
||||||
|
disabled={!editable}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
handleChange(
|
||||||
|
"errorCodeMapping",
|
||||||
|
checked
|
||||||
|
? JSON.stringify(
|
||||||
|
errorMappingExampleValue,
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
: "null",
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
className="nowheel nopan"
|
|
||||||
fontSize={8}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
{inputs.errorCodeMapping !== "null" && (
|
||||||
</div>
|
<div>
|
||||||
<Separator />
|
<CodeEditor
|
||||||
<div className="flex items-center justify-between">
|
language="json"
|
||||||
<div className="flex gap-2">
|
value={inputs.errorCodeMapping}
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
onChange={(value) => {
|
||||||
Continue on Failure
|
handleChange("errorCodeMapping", value);
|
||||||
</Label>
|
}}
|
||||||
<HelpTooltip
|
className="nowheel nopan"
|
||||||
content={helpTooltips["login"]["continueOnFailure"]}
|
fontSize={8}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-52">
|
<Separator />
|
||||||
<Switch
|
<div className="flex items-center justify-between">
|
||||||
checked={inputs.continueOnFailure}
|
<div className="flex gap-2">
|
||||||
onCheckedChange={(checked) => {
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
handleChange("continueOnFailure", checked);
|
Continue on Failure
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["login"]["continueOnFailure"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-52">
|
||||||
|
<Switch
|
||||||
|
checked={inputs.continueOnFailure}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
handleChange("continueOnFailure", checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
Cache Actions
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["login"]["cacheActions"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-52">
|
||||||
|
<Switch
|
||||||
|
checked={inputs.cacheActions}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
handleChange("cacheActions", checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs text-slate-300">
|
||||||
|
2FA Identifier
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["login"]["totpIdentifier"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<WorkflowBlockInputTextarea
|
||||||
|
nodeId={id}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("totpIdentifier", value);
|
||||||
}}
|
}}
|
||||||
|
value={inputs.totpIdentifier ?? ""}
|
||||||
|
placeholder={placeholders["login"]["totpIdentifier"]}
|
||||||
|
className="nopan text-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex gap-2">
|
||||||
<div className="flex gap-2">
|
<Label className="text-xs text-slate-300">
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
2FA Verification URL
|
||||||
Cache Actions
|
</Label>
|
||||||
</Label>
|
<HelpTooltip
|
||||||
<HelpTooltip
|
content={helpTooltips["login"]["totpVerificationUrl"]}
|
||||||
content={helpTooltips["login"]["cacheActions"]}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<WorkflowBlockInputTextarea
|
||||||
<div className="w-52">
|
nodeId={id}
|
||||||
<Switch
|
onChange={(value) => {
|
||||||
checked={inputs.cacheActions}
|
handleChange("totpVerificationUrl", value);
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
handleChange("cacheActions", checked);
|
|
||||||
}}
|
}}
|
||||||
|
value={inputs.totpVerificationUrl ?? ""}
|
||||||
|
placeholder={placeholders["login"]["totpVerificationUrl"]}
|
||||||
|
className="nopan text-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
</AccordionContent>
|
||||||
<div className="space-y-2">
|
</AccordionItem>
|
||||||
<div className="flex gap-2">
|
</Accordion>
|
||||||
<Label className="text-xs text-slate-300">
|
</div>
|
||||||
2FA Identifier
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["login"]["totpIdentifier"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<WorkflowBlockInputTextarea
|
|
||||||
nodeId={id}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("totpIdentifier", value);
|
|
||||||
}}
|
|
||||||
value={inputs.totpIdentifier ?? ""}
|
|
||||||
placeholder={placeholders["login"]["totpIdentifier"]}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs text-slate-300">
|
|
||||||
2FA Verification URL
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["login"]["totpVerificationUrl"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<WorkflowBlockInputTextarea
|
|
||||||
nodeId={id}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("totpVerificationUrl", value);
|
|
||||||
}}
|
|
||||||
value={inputs.totpVerificationUrl ?? ""}
|
|
||||||
placeholder={placeholders["login"]["totpVerificationUrl"]}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<BlockCodeEditor blockLabel={label} blockType={type} script={script} />
|
||||||
|
</Flippable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,16 @@ import { DotsHorizontalIcon } from "@radix-ui/react-icons";
|
|||||||
import { OrgWalled } from "@/components/Orgwalled";
|
import { OrgWalled } from "@/components/Orgwalled";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
isScriptable?: boolean;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
onShowScript?: () => void;
|
onShowScript?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
function NodeActionMenu({ onDelete, onShowScript }: Props) {
|
function NodeActionMenu({
|
||||||
|
isScriptable = false,
|
||||||
|
onDelete,
|
||||||
|
onShowScript,
|
||||||
|
}: Props) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
@@ -30,17 +35,19 @@ function NodeActionMenu({ onDelete, onShowScript }: Props) {
|
|||||||
>
|
>
|
||||||
Delete Block
|
Delete Block
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<OrgWalled className="p-0">
|
{isScriptable && (
|
||||||
{onShowScript && (
|
<OrgWalled className="p-0">
|
||||||
<DropdownMenuItem
|
{onShowScript && (
|
||||||
onSelect={() => {
|
<DropdownMenuItem
|
||||||
onShowScript();
|
onSelect={() => {
|
||||||
}}
|
onShowScript();
|
||||||
>
|
}}
|
||||||
Show Script
|
>
|
||||||
</DropdownMenuItem>
|
Show Script
|
||||||
)}
|
</DropdownMenuItem>
|
||||||
</OrgWalled>
|
)}
|
||||||
|
</OrgWalled>
|
||||||
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Flippable } from "@/components/Flippable";
|
||||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
@@ -12,7 +14,9 @@ import { Separator } from "@/components/ui/separator";
|
|||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { WorkflowBlockInput } from "@/components/WorkflowBlockInput";
|
import { WorkflowBlockInput } from "@/components/WorkflowBlockInput";
|
||||||
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
||||||
|
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
|
||||||
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
||||||
|
import { useBlockScriptStore } from "@/store/BlockScriptStore";
|
||||||
import {
|
import {
|
||||||
Handle,
|
Handle,
|
||||||
NodeProps,
|
NodeProps,
|
||||||
@@ -39,7 +43,10 @@ import { useParams } from "react-router-dom";
|
|||||||
|
|
||||||
function TaskNode({ id, data, type }: NodeProps<TaskNode>) {
|
function TaskNode({ id, data, type }: NodeProps<TaskNode>) {
|
||||||
const { updateNodeData } = useReactFlow();
|
const { updateNodeData } = useReactFlow();
|
||||||
|
const [facing, setFacing] = useState<"front" | "back">("front");
|
||||||
|
const blockScriptStore = useBlockScriptStore();
|
||||||
const { debuggable, editable, label } = data;
|
const { debuggable, editable, label } = data;
|
||||||
|
const script = blockScriptStore.scripts[label];
|
||||||
const debugStore = useDebugStore();
|
const debugStore = useDebugStore();
|
||||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||||
const { blockLabel: urlBlockLabel } = useParams();
|
const { blockLabel: urlBlockLabel } = useParams();
|
||||||
@@ -78,377 +85,390 @@ function TaskNode({ id, data, type }: NodeProps<TaskNode>) {
|
|||||||
updateNodeData(id, { [key]: value });
|
updateNodeData(id, { [key]: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFacing(data.showCode ? "back" : "front");
|
||||||
|
}, [data.showCode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Flippable facing={facing} preserveFrontsideHeight={true}>
|
||||||
<Handle
|
<div>
|
||||||
type="source"
|
<Handle
|
||||||
position={Position.Bottom}
|
type="source"
|
||||||
id="a"
|
position={Position.Bottom}
|
||||||
className="opacity-0"
|
id="a"
|
||||||
/>
|
className="opacity-0"
|
||||||
<Handle
|
|
||||||
type="target"
|
|
||||||
position={Position.Top}
|
|
||||||
id="b"
|
|
||||||
className="opacity-0"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
|
||||||
{
|
|
||||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
|
||||||
thisBlockIsPlaying,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<NodeHeader
|
|
||||||
blockLabel={label}
|
|
||||||
disabled={elideFromDebugging}
|
|
||||||
editable={editable}
|
|
||||||
nodeId={id}
|
|
||||||
totpIdentifier={inputs.totpIdentifier}
|
|
||||||
totpUrl={inputs.totpVerificationUrl}
|
|
||||||
type={type}
|
|
||||||
/>
|
/>
|
||||||
<Accordion type="multiple" defaultValue={["content", "extraction"]}>
|
<Handle
|
||||||
<AccordionItem value="content">
|
type="target"
|
||||||
<AccordionTrigger>Content</AccordionTrigger>
|
position={Position.Top}
|
||||||
<AccordionContent className="pl-[1.5rem] pr-1">
|
id="b"
|
||||||
<div className="space-y-4">
|
className="opacity-0"
|
||||||
<div className="space-y-2">
|
/>
|
||||||
<div className="flex justify-between">
|
<div
|
||||||
<div className="flex gap-2">
|
className={cn(
|
||||||
<Label className="text-xs text-slate-300">URL</Label>
|
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||||
<HelpTooltip content={helpTooltips["task"]["url"]} />
|
{
|
||||||
</div>
|
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||||
{isFirstWorkflowBlock ? (
|
thisBlockIsPlaying,
|
||||||
<div className="flex justify-end text-xs text-slate-400">
|
},
|
||||||
Tip: Use the {"+"} button to add parameters!
|
)}
|
||||||
|
>
|
||||||
|
<NodeHeader
|
||||||
|
blockLabel={label}
|
||||||
|
disabled={elideFromDebugging}
|
||||||
|
editable={editable}
|
||||||
|
nodeId={id}
|
||||||
|
totpIdentifier={inputs.totpIdentifier}
|
||||||
|
totpUrl={inputs.totpVerificationUrl}
|
||||||
|
type={type}
|
||||||
|
/>
|
||||||
|
<Accordion type="multiple" defaultValue={["content", "extraction"]}>
|
||||||
|
<AccordionItem value="content">
|
||||||
|
<AccordionTrigger>Content</AccordionTrigger>
|
||||||
|
<AccordionContent className="pl-[1.5rem] pr-1">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs text-slate-300">URL</Label>
|
||||||
|
<HelpTooltip content={helpTooltips["task"]["url"]} />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
{isFirstWorkflowBlock ? (
|
||||||
</div>
|
<div className="flex justify-end text-xs text-slate-400">
|
||||||
<WorkflowBlockInputTextarea
|
Tip: Use the {"+"} button to add parameters!
|
||||||
canWriteTitle={true}
|
</div>
|
||||||
nodeId={id}
|
) : null}
|
||||||
onChange={(value) => {
|
</div>
|
||||||
handleChange("url", value);
|
<WorkflowBlockInputTextarea
|
||||||
}}
|
canWriteTitle={true}
|
||||||
value={inputs.url}
|
nodeId={id}
|
||||||
placeholder={placeholders["task"]["url"]}
|
onChange={(value) => {
|
||||||
className="nopan text-xs"
|
handleChange("url", value);
|
||||||
/>
|
}}
|
||||||
</div>
|
value={inputs.url}
|
||||||
<div className="space-y-2">
|
placeholder={placeholders["task"]["url"]}
|
||||||
<div className="flex gap-2">
|
className="nopan text-xs"
|
||||||
<Label className="text-xs text-slate-300">Goal</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["task"]["navigationGoal"]}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<WorkflowBlockInputTextarea
|
<div className="space-y-2">
|
||||||
nodeId={id}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("navigationGoal", value);
|
|
||||||
}}
|
|
||||||
value={inputs.navigationGoal}
|
|
||||||
placeholder={placeholders["task"]["navigationGoal"]}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<ParametersMultiSelect
|
|
||||||
availableOutputParameters={outputParameterKeys}
|
|
||||||
parameters={data.parameterKeys}
|
|
||||||
onParametersChange={(parameterKeys) => {
|
|
||||||
updateNodeData(id, { parameterKeys });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem value="extraction">
|
|
||||||
<AccordionTrigger>Extraction</AccordionTrigger>
|
|
||||||
<AccordionContent className="pl-[1.5rem] pr-1">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs text-slate-300">
|
|
||||||
Data Extraction Goal
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["task"]["dataExtractionGoal"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<WorkflowBlockInputTextarea
|
|
||||||
nodeId={id}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("dataExtractionGoal", value);
|
|
||||||
}}
|
|
||||||
value={inputs.dataExtractionGoal}
|
|
||||||
placeholder={placeholders["task"]["dataExtractionGoal"]}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<WorkflowDataSchemaInputGroup
|
|
||||||
exampleValue={dataSchemaExampleValue}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("dataSchema", value);
|
|
||||||
}}
|
|
||||||
value={inputs.dataSchema}
|
|
||||||
suggestionContext={{
|
|
||||||
data_extraction_goal: inputs.dataExtractionGoal,
|
|
||||||
current_schema: inputs.dataSchema,
|
|
||||||
navigation_goal: inputs.navigationGoal,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem value="advanced" className="border-b-0">
|
|
||||||
<AccordionTrigger>Advanced Settings</AccordionTrigger>
|
|
||||||
<AccordionContent className="pl-6 pr-1 pt-1">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs text-slate-300">
|
|
||||||
Complete if...
|
|
||||||
</Label>
|
|
||||||
<WorkflowBlockInputTextarea
|
|
||||||
nodeId={id}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("completeCriterion", value);
|
|
||||||
}}
|
|
||||||
value={inputs.completeCriterion}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
|
||||||
<ModelSelector
|
|
||||||
className="nopan w-52 text-xs"
|
|
||||||
value={inputs.model}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("model", value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
|
||||||
Engine
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<RunEngineSelector
|
|
||||||
value={inputs.engine}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("engine", value);
|
|
||||||
}}
|
|
||||||
className="nopan w-52 text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
|
||||||
Max Steps Override
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["task"]["maxStepsOverride"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
placeholder={placeholders["task"]["maxStepsOverride"]}
|
|
||||||
className="nopan w-52 text-xs"
|
|
||||||
min="0"
|
|
||||||
value={inputs.maxStepsOverride ?? ""}
|
|
||||||
onChange={(event) => {
|
|
||||||
const value =
|
|
||||||
event.target.value === ""
|
|
||||||
? null
|
|
||||||
: Number(event.target.value);
|
|
||||||
handleChange("maxStepsOverride", value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex gap-4">
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
<Label className="text-xs text-slate-300">Goal</Label>
|
||||||
Error Messages
|
<HelpTooltip
|
||||||
|
content={helpTooltips["task"]["navigationGoal"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<WorkflowBlockInputTextarea
|
||||||
|
nodeId={id}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("navigationGoal", value);
|
||||||
|
}}
|
||||||
|
value={inputs.navigationGoal}
|
||||||
|
placeholder={placeholders["task"]["navigationGoal"]}
|
||||||
|
className="nopan text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<ParametersMultiSelect
|
||||||
|
availableOutputParameters={outputParameterKeys}
|
||||||
|
parameters={data.parameterKeys}
|
||||||
|
onParametersChange={(parameterKeys) => {
|
||||||
|
updateNodeData(id, { parameterKeys });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
<AccordionItem value="extraction">
|
||||||
|
<AccordionTrigger>Extraction</AccordionTrigger>
|
||||||
|
<AccordionContent className="pl-[1.5rem] pr-1">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs text-slate-300">
|
||||||
|
Data Extraction Goal
|
||||||
</Label>
|
</Label>
|
||||||
<HelpTooltip
|
<HelpTooltip
|
||||||
content={helpTooltips["task"]["errorCodeMapping"]}
|
content={helpTooltips["task"]["dataExtractionGoal"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Checkbox
|
<WorkflowBlockInputTextarea
|
||||||
checked={inputs.errorCodeMapping !== "null"}
|
nodeId={id}
|
||||||
disabled={!editable}
|
onChange={(value) => {
|
||||||
onCheckedChange={(checked) => {
|
handleChange("dataExtractionGoal", value);
|
||||||
handleChange(
|
|
||||||
"errorCodeMapping",
|
|
||||||
checked
|
|
||||||
? JSON.stringify(errorMappingExampleValue, null, 2)
|
|
||||||
: "null",
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
|
value={inputs.dataExtractionGoal}
|
||||||
|
placeholder={placeholders["task"]["dataExtractionGoal"]}
|
||||||
|
className="nopan text-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{inputs.errorCodeMapping !== "null" && (
|
<WorkflowDataSchemaInputGroup
|
||||||
<div>
|
exampleValue={dataSchemaExampleValue}
|
||||||
<CodeEditor
|
onChange={(value) => {
|
||||||
language="json"
|
handleChange("dataSchema", value);
|
||||||
value={inputs.errorCodeMapping}
|
}}
|
||||||
onChange={(value) => {
|
value={inputs.dataSchema}
|
||||||
handleChange("errorCodeMapping", value);
|
suggestionContext={{
|
||||||
}}
|
data_extraction_goal: inputs.dataExtractionGoal,
|
||||||
className="nowheel nopan"
|
current_schema: inputs.dataSchema,
|
||||||
fontSize={8}
|
navigation_goal: inputs.navigationGoal,
|
||||||
/>
|
}}
|
||||||
</div>
|
/>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
</AccordionContent>
|
||||||
<div className="flex items-center justify-between">
|
</AccordionItem>
|
||||||
<div className="flex gap-2">
|
<AccordionItem value="advanced" className="border-b-0">
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
<AccordionTrigger>Advanced Settings</AccordionTrigger>
|
||||||
Include Action History
|
<AccordionContent className="pl-6 pr-1 pt-1">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="text-xs text-slate-300">
|
||||||
|
Complete if...
|
||||||
</Label>
|
</Label>
|
||||||
<HelpTooltip
|
<WorkflowBlockInputTextarea
|
||||||
content={
|
nodeId={id}
|
||||||
helpTooltips["task"][
|
onChange={(value) => {
|
||||||
"includeActionHistoryInVerification"
|
handleChange("completeCriterion", value);
|
||||||
]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-52">
|
|
||||||
<Switch
|
|
||||||
checked={inputs.includeActionHistoryInVerification}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
handleChange(
|
|
||||||
"includeActionHistoryInVerification",
|
|
||||||
checked,
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
|
value={inputs.completeCriterion}
|
||||||
|
className="nopan text-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<Separator />
|
||||||
<div className="flex items-center justify-between">
|
<ModelSelector
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
|
||||||
Continue on Failure
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["task"]["continueOnFailure"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-52">
|
|
||||||
<Switch
|
|
||||||
checked={inputs.continueOnFailure}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
handleChange("continueOnFailure", checked);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
|
||||||
Cache Actions
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["task"]["cacheActions"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-52">
|
|
||||||
<Switch
|
|
||||||
checked={inputs.cacheActions}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
handleChange("cacheActions", checked);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
|
||||||
Complete on Download
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip
|
|
||||||
content={helpTooltips["task"]["completeOnDownload"]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-52">
|
|
||||||
<Switch
|
|
||||||
checked={inputs.allowDownloads}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
handleChange("allowDownloads", checked);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
|
||||||
File Suffix
|
|
||||||
</Label>
|
|
||||||
<HelpTooltip content={helpTooltips["task"]["fileSuffix"]} />
|
|
||||||
</div>
|
|
||||||
<WorkflowBlockInput
|
|
||||||
nodeId={id}
|
|
||||||
type="text"
|
|
||||||
placeholder={placeholders["task"]["downloadSuffix"]}
|
|
||||||
className="nopan w-52 text-xs"
|
className="nopan w-52 text-xs"
|
||||||
value={inputs.downloadSuffix ?? ""}
|
value={inputs.model}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
handleChange("downloadSuffix", value);
|
handleChange("model", value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
<div className="flex items-center justify-between">
|
||||||
<Separator />
|
<div className="flex gap-2">
|
||||||
<div className="space-y-2">
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
<div className="flex gap-2">
|
Engine
|
||||||
<Label className="text-xs text-slate-300">
|
</Label>
|
||||||
2FA Identifier
|
</div>
|
||||||
</Label>
|
<RunEngineSelector
|
||||||
<HelpTooltip
|
value={inputs.engine}
|
||||||
content={helpTooltips["task"]["totpIdentifier"]}
|
onChange={(value) => {
|
||||||
|
handleChange("engine", value);
|
||||||
|
}}
|
||||||
|
className="nopan w-52 text-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<WorkflowBlockInputTextarea
|
<div className="flex items-center justify-between">
|
||||||
nodeId={id}
|
<div className="flex gap-2">
|
||||||
onChange={(value) => {
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
handleChange("totpIdentifier", value);
|
Max Steps Override
|
||||||
}}
|
</Label>
|
||||||
value={inputs.totpIdentifier ?? ""}
|
<HelpTooltip
|
||||||
placeholder={placeholders["task"]["totpIdentifier"]}
|
content={helpTooltips["task"]["maxStepsOverride"]}
|
||||||
className="nopan text-xs"
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<Input
|
||||||
<div className="space-y-2">
|
type="number"
|
||||||
<div className="flex gap-2">
|
placeholder={placeholders["task"]["maxStepsOverride"]}
|
||||||
<Label className="text-xs text-slate-300">
|
className="nopan w-52 text-xs"
|
||||||
2FA Verification URL
|
min="0"
|
||||||
</Label>
|
value={inputs.maxStepsOverride ?? ""}
|
||||||
<HelpTooltip
|
onChange={(event) => {
|
||||||
content={helpTooltips["task"]["totpVerificationUrl"]}
|
const value =
|
||||||
|
event.target.value === ""
|
||||||
|
? null
|
||||||
|
: Number(event.target.value);
|
||||||
|
handleChange("maxStepsOverride", value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
Error Messages
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["task"]["errorCodeMapping"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
checked={inputs.errorCodeMapping !== "null"}
|
||||||
|
disabled={!editable}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
handleChange(
|
||||||
|
"errorCodeMapping",
|
||||||
|
checked
|
||||||
|
? JSON.stringify(
|
||||||
|
errorMappingExampleValue,
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
: "null",
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{inputs.errorCodeMapping !== "null" && (
|
||||||
|
<div>
|
||||||
|
<CodeEditor
|
||||||
|
language="json"
|
||||||
|
value={inputs.errorCodeMapping}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("errorCodeMapping", value);
|
||||||
|
}}
|
||||||
|
className="nowheel nopan"
|
||||||
|
fontSize={8}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
Include Action History
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={
|
||||||
|
helpTooltips["task"][
|
||||||
|
"includeActionHistoryInVerification"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-52">
|
||||||
|
<Switch
|
||||||
|
checked={inputs.includeActionHistoryInVerification}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
handleChange(
|
||||||
|
"includeActionHistoryInVerification",
|
||||||
|
checked,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
Continue on Failure
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["task"]["continueOnFailure"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-52">
|
||||||
|
<Switch
|
||||||
|
checked={inputs.continueOnFailure}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
handleChange("continueOnFailure", checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
Cache Actions
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["task"]["cacheActions"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-52">
|
||||||
|
<Switch
|
||||||
|
checked={inputs.cacheActions}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
handleChange("cacheActions", checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
Complete on Download
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["task"]["completeOnDownload"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-52">
|
||||||
|
<Switch
|
||||||
|
checked={inputs.allowDownloads}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
handleChange("allowDownloads", checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
|
File Suffix
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["task"]["fileSuffix"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<WorkflowBlockInput
|
||||||
|
nodeId={id}
|
||||||
|
type="text"
|
||||||
|
placeholder={placeholders["task"]["downloadSuffix"]}
|
||||||
|
className="nopan w-52 text-xs"
|
||||||
|
value={inputs.downloadSuffix ?? ""}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("downloadSuffix", value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs text-slate-300">
|
||||||
|
2FA Identifier
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["task"]["totpIdentifier"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<WorkflowBlockInputTextarea
|
||||||
|
nodeId={id}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("totpIdentifier", value);
|
||||||
|
}}
|
||||||
|
value={inputs.totpIdentifier ?? ""}
|
||||||
|
placeholder={placeholders["task"]["totpIdentifier"]}
|
||||||
|
className="nopan text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label className="text-xs text-slate-300">
|
||||||
|
2FA Verification URL
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={helpTooltips["task"]["totpVerificationUrl"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<WorkflowBlockInputTextarea
|
||||||
|
nodeId={id}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("totpVerificationUrl", value);
|
||||||
|
}}
|
||||||
|
value={inputs.totpVerificationUrl ?? ""}
|
||||||
|
placeholder={placeholders["task"]["totpVerificationUrl"]}
|
||||||
|
className="nopan text-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<WorkflowBlockInputTextarea
|
|
||||||
nodeId={id}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("totpVerificationUrl", value);
|
|
||||||
}}
|
|
||||||
value={inputs.totpVerificationUrl ?? ""}
|
|
||||||
placeholder={placeholders["task"]["totpVerificationUrl"]}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</AccordionContent>
|
||||||
</AccordionContent>
|
</AccordionItem>
|
||||||
</AccordionItem>
|
</Accordion>
|
||||||
</Accordion>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<BlockCodeEditor blockLabel={label} blockType={type} script={script} />
|
||||||
|
</Flippable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Flippable } from "@/components/Flippable";
|
||||||
import { Handle, NodeProps, Position, useReactFlow } from "@xyflow/react";
|
import { Handle, NodeProps, Position, useReactFlow } from "@xyflow/react";
|
||||||
import type { URLNode } from "./types";
|
import type { URLNode } from "./types";
|
||||||
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
|
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
||||||
|
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
|
||||||
import { placeholders } from "../../helpContent";
|
import { placeholders } from "../../helpContent";
|
||||||
|
import { useBlockScriptStore } from "@/store/BlockScriptStore";
|
||||||
import { useDebugStore } from "@/store/useDebugStore";
|
import { useDebugStore } from "@/store/useDebugStore";
|
||||||
import { cn } from "@/util/utils";
|
import { cn } from "@/util/utils";
|
||||||
import { NodeHeader } from "../components/NodeHeader";
|
import { NodeHeader } from "../components/NodeHeader";
|
||||||
@@ -12,7 +16,10 @@ import { useParams } from "react-router-dom";
|
|||||||
|
|
||||||
function URLNode({ id, data, type }: NodeProps<URLNode>) {
|
function URLNode({ id, data, type }: NodeProps<URLNode>) {
|
||||||
const { updateNodeData } = useReactFlow();
|
const { updateNodeData } = useReactFlow();
|
||||||
|
const [facing, setFacing] = useState<"front" | "back">("front");
|
||||||
|
const blockScriptStore = useBlockScriptStore();
|
||||||
const { debuggable, editable, label } = data;
|
const { debuggable, editable, label } = data;
|
||||||
|
const script = blockScriptStore.scripts[label];
|
||||||
const debugStore = useDebugStore();
|
const debugStore = useDebugStore();
|
||||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||||
const { blockLabel: urlBlockLabel } = useParams();
|
const { blockLabel: urlBlockLabel } = useParams();
|
||||||
@@ -32,62 +39,73 @@ function URLNode({ id, data, type }: NodeProps<URLNode>) {
|
|||||||
updateNodeData(id, { [key]: value });
|
updateNodeData(id, { [key]: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFacing(data.showCode ? "back" : "front");
|
||||||
|
}, [data.showCode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Flippable facing={facing} preserveFrontsideHeight={true}>
|
||||||
<Handle
|
<div>
|
||||||
type="source"
|
<Handle
|
||||||
position={Position.Bottom}
|
type="source"
|
||||||
id="a"
|
position={Position.Bottom}
|
||||||
className="opacity-0"
|
id="a"
|
||||||
/>
|
className="opacity-0"
|
||||||
<Handle
|
|
||||||
type="target"
|
|
||||||
position={Position.Top}
|
|
||||||
id="b"
|
|
||||||
className="opacity-0"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
|
||||||
{
|
|
||||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
|
||||||
thisBlockIsPlaying,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<NodeHeader
|
|
||||||
blockLabel={label}
|
|
||||||
disabled={elideFromDebugging}
|
|
||||||
editable={editable}
|
|
||||||
nodeId={id}
|
|
||||||
totpIdentifier={null}
|
|
||||||
totpUrl={null}
|
|
||||||
type="goto_url"
|
|
||||||
/>
|
/>
|
||||||
<div className="space-y-4">
|
<Handle
|
||||||
<div className="space-y-2">
|
type="target"
|
||||||
<div className="flex justify-between">
|
position={Position.Top}
|
||||||
<Label className="text-xs text-slate-300">URL</Label>
|
id="b"
|
||||||
{isFirstWorkflowBlock ? (
|
className="opacity-0"
|
||||||
<div className="flex justify-end text-xs text-slate-400">
|
/>
|
||||||
Tip: Use the {"+"} button to add parameters!
|
<div
|
||||||
</div>
|
className={cn(
|
||||||
) : null}
|
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||||
|
{
|
||||||
|
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||||
|
thisBlockIsPlaying,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<NodeHeader
|
||||||
|
blockLabel={label}
|
||||||
|
disabled={elideFromDebugging}
|
||||||
|
editable={editable}
|
||||||
|
nodeId={id}
|
||||||
|
totpIdentifier={null}
|
||||||
|
totpUrl={null}
|
||||||
|
type="goto_url"
|
||||||
|
/>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<Label className="text-xs text-slate-300">URL</Label>
|
||||||
|
{isFirstWorkflowBlock ? (
|
||||||
|
<div className="flex justify-end text-xs text-slate-400">
|
||||||
|
Tip: Use the {"+"} button to add parameters!
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<WorkflowBlockInputTextarea
|
||||||
|
canWriteTitle={true}
|
||||||
|
nodeId={id}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("url", value);
|
||||||
|
}}
|
||||||
|
value={inputs.url}
|
||||||
|
placeholder={placeholders[type]["url"]}
|
||||||
|
className="nopan text-xs"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<WorkflowBlockInputTextarea
|
|
||||||
canWriteTitle={true}
|
|
||||||
nodeId={id}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("url", value);
|
|
||||||
}}
|
|
||||||
value={inputs.url}
|
|
||||||
placeholder={placeholders[type]["url"]}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<BlockCodeEditor
|
||||||
|
blockLabel={label}
|
||||||
|
blockType="goto_url" // sic: the naming is inconsistent
|
||||||
|
script={script}
|
||||||
|
/>
|
||||||
|
</Flippable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Flippable } from "@/components/Flippable";
|
||||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
@@ -10,7 +12,9 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
||||||
|
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
|
||||||
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
||||||
|
import { useBlockScriptStore } from "@/store/BlockScriptStore";
|
||||||
import {
|
import {
|
||||||
Handle,
|
Handle,
|
||||||
NodeProps,
|
NodeProps,
|
||||||
@@ -35,7 +39,10 @@ import { useParams } from "react-router-dom";
|
|||||||
|
|
||||||
function ValidationNode({ id, data, type }: NodeProps<ValidationNode>) {
|
function ValidationNode({ id, data, type }: NodeProps<ValidationNode>) {
|
||||||
const { updateNodeData } = useReactFlow();
|
const { updateNodeData } = useReactFlow();
|
||||||
|
const [facing, setFacing] = useState<"front" | "back">("front");
|
||||||
|
const blockScriptStore = useBlockScriptStore();
|
||||||
const { debuggable, editable, label } = data;
|
const { debuggable, editable, label } = data;
|
||||||
|
const script = blockScriptStore.scripts[label];
|
||||||
const debugStore = useDebugStore();
|
const debugStore = useDebugStore();
|
||||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||||
const { blockLabel: urlBlockLabel } = useParams();
|
const { blockLabel: urlBlockLabel } = useParams();
|
||||||
@@ -61,162 +68,177 @@ function ValidationNode({ id, data, type }: NodeProps<ValidationNode>) {
|
|||||||
|
|
||||||
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
|
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFacing(data.showCode ? "back" : "front");
|
||||||
|
}, [data.showCode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Flippable facing={facing} preserveFrontsideHeight={true}>
|
||||||
<Handle
|
<div>
|
||||||
type="source"
|
<Handle
|
||||||
position={Position.Bottom}
|
type="source"
|
||||||
id="a"
|
position={Position.Bottom}
|
||||||
className="opacity-0"
|
id="a"
|
||||||
/>
|
className="opacity-0"
|
||||||
<Handle
|
|
||||||
type="target"
|
|
||||||
position={Position.Top}
|
|
||||||
id="b"
|
|
||||||
className="opacity-0"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
|
||||||
{
|
|
||||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
|
||||||
thisBlockIsPlaying,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<NodeHeader
|
|
||||||
blockLabel={label}
|
|
||||||
disabled={elideFromDebugging}
|
|
||||||
editable={editable}
|
|
||||||
nodeId={id}
|
|
||||||
totpIdentifier={null}
|
|
||||||
totpUrl={null}
|
|
||||||
type={type}
|
|
||||||
/>
|
/>
|
||||||
<div className="space-y-2">
|
<Handle
|
||||||
<div className="flex justify-between">
|
type="target"
|
||||||
<Label className="text-xs text-slate-300">Complete if...</Label>
|
position={Position.Top}
|
||||||
{isFirstWorkflowBlock ? (
|
id="b"
|
||||||
<div className="flex justify-end text-xs text-slate-400">
|
className="opacity-0"
|
||||||
Tip: Use the {"+"} button to add parameters!
|
/>
|
||||||
</div>
|
<div
|
||||||
) : null}
|
className={cn(
|
||||||
</div>
|
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||||
<WorkflowBlockInputTextarea
|
{
|
||||||
|
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||||
|
thisBlockIsPlaying,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<NodeHeader
|
||||||
|
blockLabel={label}
|
||||||
|
disabled={elideFromDebugging}
|
||||||
|
editable={editable}
|
||||||
nodeId={id}
|
nodeId={id}
|
||||||
onChange={(value) => {
|
totpIdentifier={null}
|
||||||
handleChange("completeCriterion", value);
|
totpUrl={null}
|
||||||
}}
|
type={type}
|
||||||
value={inputs.completeCriterion}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
<div className="space-y-2">
|
||||||
<div className="space-y-2">
|
<div className="flex justify-between">
|
||||||
<Label className="text-xs text-slate-300">Terminate if...</Label>
|
<Label className="text-xs text-slate-300">Complete if...</Label>
|
||||||
<WorkflowBlockInputTextarea
|
{isFirstWorkflowBlock ? (
|
||||||
nodeId={id}
|
<div className="flex justify-end text-xs text-slate-400">
|
||||||
onChange={(value) => {
|
Tip: Use the {"+"} button to add parameters!
|
||||||
handleChange("terminateCriterion", value);
|
|
||||||
}}
|
|
||||||
value={inputs.terminateCriterion}
|
|
||||||
className="nopan text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
|
||||||
<Accordion type="single" collapsible>
|
|
||||||
<AccordionItem value="advanced" className="border-b-0">
|
|
||||||
<AccordionTrigger className="py-0">
|
|
||||||
Advanced Settings
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent>
|
|
||||||
<div className="ml-6 mt-4 space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<ModelSelector
|
|
||||||
className="nopan mr-[1px] w-52 text-xs"
|
|
||||||
value={inputs.model}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleChange("model", value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ParametersMultiSelect
|
|
||||||
availableOutputParameters={outputParameterKeys}
|
|
||||||
parameters={data.parameterKeys}
|
|
||||||
onParametersChange={(parameterKeys) => {
|
|
||||||
updateNodeData(id, { parameterKeys });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
) : null}
|
||||||
<div className="flex gap-4">
|
</div>
|
||||||
<div className="flex gap-2">
|
<WorkflowBlockInputTextarea
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
nodeId={id}
|
||||||
Error Messages
|
onChange={(value) => {
|
||||||
</Label>
|
handleChange("completeCriterion", value);
|
||||||
<HelpTooltip
|
}}
|
||||||
content={helpTooltips["validation"]["errorCodeMapping"]}
|
value={inputs.completeCriterion}
|
||||||
/>
|
className="nopan text-xs"
|
||||||
</div>
|
/>
|
||||||
<Checkbox
|
</div>
|
||||||
checked={inputs.errorCodeMapping !== "null"}
|
<div className="space-y-2">
|
||||||
disabled={!editable}
|
<Label className="text-xs text-slate-300">Terminate if...</Label>
|
||||||
onCheckedChange={(checked) => {
|
<WorkflowBlockInputTextarea
|
||||||
if (!editable) {
|
nodeId={id}
|
||||||
return;
|
onChange={(value) => {
|
||||||
}
|
handleChange("terminateCriterion", value);
|
||||||
handleChange(
|
}}
|
||||||
"errorCodeMapping",
|
value={inputs.terminateCriterion}
|
||||||
checked
|
className="nopan text-xs"
|
||||||
? JSON.stringify(errorMappingExampleValue, null, 2)
|
/>
|
||||||
: "null",
|
</div>
|
||||||
);
|
<Separator />
|
||||||
|
<Accordion type="single" collapsible>
|
||||||
|
<AccordionItem value="advanced" className="border-b-0">
|
||||||
|
<AccordionTrigger className="py-0">
|
||||||
|
Advanced Settings
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="ml-6 mt-4 space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<ModelSelector
|
||||||
|
className="nopan mr-[1px] w-52 text-xs"
|
||||||
|
value={inputs.model}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("model", value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ParametersMultiSelect
|
||||||
|
availableOutputParameters={outputParameterKeys}
|
||||||
|
parameters={data.parameterKeys}
|
||||||
|
onParametersChange={(parameterKeys) => {
|
||||||
|
updateNodeData(id, { parameterKeys });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{inputs.errorCodeMapping !== "null" && (
|
<div className="space-y-2">
|
||||||
<div>
|
<div className="flex gap-4">
|
||||||
<CodeEditor
|
<div className="flex gap-2">
|
||||||
language="json"
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
value={inputs.errorCodeMapping}
|
Error Messages
|
||||||
onChange={(value) => {
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={
|
||||||
|
helpTooltips["validation"]["errorCodeMapping"]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
checked={inputs.errorCodeMapping !== "null"}
|
||||||
|
disabled={!editable}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
if (!editable) {
|
if (!editable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleChange("errorCodeMapping", value);
|
handleChange(
|
||||||
|
"errorCodeMapping",
|
||||||
|
checked
|
||||||
|
? JSON.stringify(
|
||||||
|
errorMappingExampleValue,
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
: "null",
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
className="nowheel nopan"
|
|
||||||
fontSize={8}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
{inputs.errorCodeMapping !== "null" && (
|
||||||
</div>
|
<div>
|
||||||
<Separator />
|
<CodeEditor
|
||||||
<div className="flex items-center justify-between">
|
language="json"
|
||||||
<div className="flex gap-2">
|
value={inputs.errorCodeMapping}
|
||||||
<Label className="text-xs font-normal text-slate-300">
|
onChange={(value) => {
|
||||||
Continue on Failure
|
if (!editable) {
|
||||||
</Label>
|
return;
|
||||||
<HelpTooltip
|
}
|
||||||
content={helpTooltips["validation"]["continueOnFailure"]}
|
handleChange("errorCodeMapping", value);
|
||||||
/>
|
}}
|
||||||
|
className="nowheel nopan"
|
||||||
|
fontSize={8}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-52">
|
<Separator />
|
||||||
<Switch
|
<div className="flex items-center justify-between">
|
||||||
checked={data.continueOnFailure}
|
<div className="flex gap-2">
|
||||||
onCheckedChange={(checked) => {
|
<Label className="text-xs font-normal text-slate-300">
|
||||||
if (!editable) {
|
Continue on Failure
|
||||||
return;
|
</Label>
|
||||||
|
<HelpTooltip
|
||||||
|
content={
|
||||||
|
helpTooltips["validation"]["continueOnFailure"]
|
||||||
}
|
}
|
||||||
updateNodeData(id, { continueOnFailure: checked });
|
/>
|
||||||
}}
|
</div>
|
||||||
/>
|
<div className="w-52">
|
||||||
|
<Switch
|
||||||
|
checked={data.continueOnFailure}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
if (!editable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateNodeData(id, { continueOnFailure: checked });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</AccordionContent>
|
||||||
</AccordionContent>
|
</AccordionItem>
|
||||||
</AccordionItem>
|
</Accordion>
|
||||||
</Accordion>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<BlockCodeEditor blockLabel={label} blockType={type} script={script} />
|
||||||
|
</Flippable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { useDebugSessionQuery } from "@/routes/workflows/hooks/useDebugSessionQu
|
|||||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||||
import {
|
import {
|
||||||
debuggableWorkflowBlockTypes,
|
debuggableWorkflowBlockTypes,
|
||||||
|
scriptableWorkflowBlockTypes,
|
||||||
type WorkflowBlockType,
|
type WorkflowBlockType,
|
||||||
type WorkflowApiResponse,
|
type WorkflowApiResponse,
|
||||||
} from "@/routes/workflows/types/workflowTypes";
|
} from "@/routes/workflows/types/workflowTypes";
|
||||||
@@ -154,6 +155,7 @@ function NodeHeader({
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isDebuggable = debuggableWorkflowBlockTypes.has(type);
|
const isDebuggable = debuggableWorkflowBlockTypes.has(type);
|
||||||
|
const isScriptable = scriptableWorkflowBlockTypes.has(type);
|
||||||
const { data: workflowRun } = useWorkflowRunQuery();
|
const { data: workflowRun } = useWorkflowRunQuery();
|
||||||
const workflowRunIsRunningOrQueued =
|
const workflowRunIsRunningOrQueued =
|
||||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||||
@@ -414,6 +416,7 @@ function NodeHeader({
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<NodeActionMenu
|
<NodeActionMenu
|
||||||
|
isScriptable={isScriptable}
|
||||||
onDelete={() => {
|
onDelete={() => {
|
||||||
deleteNodeCallback(nodeId);
|
deleteNodeCallback(nodeId);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -228,6 +228,17 @@ export const debuggableWorkflowBlockTypes: Set<WorkflowBlockType> = new Set([
|
|||||||
"validation",
|
"validation",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const scriptableWorkflowBlockTypes: Set<WorkflowBlockType> = new Set([
|
||||||
|
"action",
|
||||||
|
"extraction",
|
||||||
|
"file_download",
|
||||||
|
"goto_url",
|
||||||
|
"login",
|
||||||
|
"navigation",
|
||||||
|
"task",
|
||||||
|
"validation",
|
||||||
|
]);
|
||||||
|
|
||||||
export function isTaskVariantBlock(item: {
|
export function isTaskVariantBlock(item: {
|
||||||
block_type: WorkflowBlockType;
|
block_type: WorkflowBlockType;
|
||||||
}): boolean {
|
}): boolean {
|
||||||
|
|||||||
Reference in New Issue
Block a user