Jon/sky 5820 make browser task block flippable with code (#3165)
This commit is contained in:
@@ -10,7 +10,7 @@ import {
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { useOnChange } from "@/hooks/useOnChange";
|
||||
import { useShouldNotifyWhenClosingTab } from "@/hooks/useShouldNotifyWhenClosingTab";
|
||||
import { DeleteNodeCallbackContext } from "@/store/DeleteNodeCallbackContext";
|
||||
import { BlockActionContext } from "@/store/BlockActionContext";
|
||||
import { useDebugStore } from "@/store/useDebugStore";
|
||||
import {
|
||||
useWorkflowHasChangesStore,
|
||||
@@ -543,6 +543,37 @@ function FlowRenderer({
|
||||
doLayout(newNodesWithUpdatedParameters, newEdges);
|
||||
}
|
||||
|
||||
function toggleScript({
|
||||
id,
|
||||
label,
|
||||
show,
|
||||
}: {
|
||||
id?: string;
|
||||
label?: string;
|
||||
show: boolean;
|
||||
}) {
|
||||
if (id) {
|
||||
const node = nodes.find((node) => node.id === id);
|
||||
if (!node || !isWorkflowBlockNode(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.data.showCode = show;
|
||||
} else if (label) {
|
||||
const node = nodes.find(
|
||||
(node) => "label" in node.data && node.data.label === label,
|
||||
);
|
||||
|
||||
if (!node || !isWorkflowBlockNode(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.data.showCode = show;
|
||||
}
|
||||
|
||||
doLayout(nodes, edges);
|
||||
}
|
||||
|
||||
const editorElementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useAutoPan(editorElementRef, nodes);
|
||||
@@ -642,7 +673,12 @@ function FlowRenderer({
|
||||
<WorkflowParametersStateContext.Provider
|
||||
value={[parameters, setParameters]}
|
||||
>
|
||||
<DeleteNodeCallbackContext.Provider value={deleteNode}>
|
||||
<BlockActionContext.Provider
|
||||
value={{
|
||||
deleteNodeCallback: deleteNode,
|
||||
toggleScriptForNodeCallback: toggleScript,
|
||||
}}
|
||||
>
|
||||
<ReactFlow
|
||||
ref={editorElementRef}
|
||||
nodes={nodes}
|
||||
@@ -772,7 +808,7 @@ function FlowRenderer({
|
||||
</Panel>
|
||||
)}
|
||||
</ReactFlow>
|
||||
</DeleteNodeCallbackContext.Provider>
|
||||
</BlockActionContext.Provider>
|
||||
</WorkflowParametersStateContext.Provider>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -24,6 +24,8 @@ import { toast } from "@/components/ui/use-toast";
|
||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||
import { useMountEffect } from "@/hooks/useMountEffect";
|
||||
import { statusIsFinalized } from "@/routes/tasks/types.ts";
|
||||
import { useBlockScriptsQuery } from "@/routes/workflows/hooks/useBlockScriptsQuery";
|
||||
import { useBlockScriptStore } from "@/store/BlockScriptStore";
|
||||
import { useSidebarStore } from "@/store/SidebarStore";
|
||||
import { useWorkflowHasChangesStore } from "@/store/WorkflowHasChangesStore";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
@@ -47,12 +49,18 @@ function WorkflowDebugger() {
|
||||
const credentialGetter = useCredentialGetter();
|
||||
const queryClient = useQueryClient();
|
||||
const [shouldFetchDebugSession, setShouldFetchDebugSession] = useState(false);
|
||||
const blockScriptStore = useBlockScriptStore();
|
||||
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
|
||||
const { data: workflow } = useWorkflowQuery({
|
||||
workflowPermanentId,
|
||||
});
|
||||
|
||||
const { data: blockScripts } = useBlockScriptsQuery({
|
||||
workflowPermanentId,
|
||||
});
|
||||
|
||||
const { data: debugSession } = useDebugSessionQuery({
|
||||
workflowPermanentId,
|
||||
enabled: shouldFetchDebugSession && !!workflowPermanentId,
|
||||
@@ -80,6 +88,11 @@ function WorkflowDebugger() {
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
blockScriptStore.setScripts(blockScripts ?? {});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [blockScripts]);
|
||||
|
||||
const afterCycleBrowser = () => {
|
||||
setOpenDialogue(false);
|
||||
setShowPowerButton(false);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { useEffect } from "react";
|
||||
import { useMountEffect } from "@/hooks/useMountEffect";
|
||||
import { useBlockScriptsQuery } from "@/routes/workflows/hooks/useBlockScriptsQuery";
|
||||
import { useBlockScriptStore } from "@/store/BlockScriptStore";
|
||||
import { useSidebarStore } from "@/store/SidebarStore";
|
||||
import { useWorkflowHasChangesStore } from "@/store/WorkflowHasChangesStore";
|
||||
import { ReactFlowProvider } from "@xyflow/react";
|
||||
@@ -17,6 +20,7 @@ function WorkflowEditor() {
|
||||
return state.setCollapsed;
|
||||
});
|
||||
const workflowChangesStore = useWorkflowHasChangesStore();
|
||||
const blockScriptStore = useBlockScriptStore();
|
||||
|
||||
const { data: workflow, isLoading } = useWorkflowQuery({
|
||||
workflowPermanentId,
|
||||
@@ -25,11 +29,20 @@ function WorkflowEditor() {
|
||||
const { data: globalWorkflows, isLoading: isGlobalWorkflowsLoading } =
|
||||
useGlobalWorkflowsQuery();
|
||||
|
||||
const { data: blockScripts } = useBlockScriptsQuery({
|
||||
workflowPermanentId,
|
||||
});
|
||||
|
||||
useMountEffect(() => {
|
||||
setCollapsed(true);
|
||||
workflowChangesStore.setHasChanges(false);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
blockScriptStore.setScripts(blockScripts ?? {});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [blockScripts]);
|
||||
|
||||
if (isLoading || isGlobalWorkflowsLoading) {
|
||||
return (
|
||||
<div className="flex h-screen w-full items-center justify-center">
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useEffect } from "react";
|
||||
import { Flippable } from "@/components/Flippable";
|
||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||
import {
|
||||
Accordion,
|
||||
@@ -12,7 +14,9 @@ import { Separator } from "@/components/ui/separator";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { WorkflowBlockInput } from "@/components/WorkflowBlockInput";
|
||||
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
|
||||
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
|
||||
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
||||
import { useBlockScriptStore } from "@/store/BlockScriptStore";
|
||||
import {
|
||||
Handle,
|
||||
NodeProps,
|
||||
@@ -40,7 +44,10 @@ function NavigationNode({ id, data, type }: NodeProps<NavigationNode>) {
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const debugStore = useDebugStore();
|
||||
const { updateNodeData } = useReactFlow();
|
||||
const [facing, setFacing] = useState<"front" | "back">("front");
|
||||
const blockScriptStore = useBlockScriptStore();
|
||||
const { editable, debuggable, label } = data;
|
||||
const script = blockScriptStore.scripts[label];
|
||||
const thisBlockIsPlaying =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
@@ -76,359 +83,382 @@ function NavigationNode({ id, data, type }: NodeProps<NavigationNode>) {
|
||||
updateNodeData(id, { [key]: value });
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Handle
|
||||
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}
|
||||
editable={editable}
|
||||
disabled={elideFromDebugging}
|
||||
nodeId={id}
|
||||
totpIdentifier={inputs.totpIdentifier}
|
||||
totpUrl={inputs.totpVerificationUrl}
|
||||
type={type}
|
||||
/>
|
||||
<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={helpTooltips["navigation"]["url"]} />
|
||||
</div>
|
||||
{isFirstWorkflowBlock ? (
|
||||
<div className="flex justify-end text-xs text-slate-400">
|
||||
Tip: Use the {"+"} button to add parameters!
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
useEffect(() => {
|
||||
setFacing(data.showCode ? "back" : "front");
|
||||
}, [data.showCode]);
|
||||
|
||||
<WorkflowBlockInputTextarea
|
||||
canWriteTitle={true}
|
||||
nodeId={id}
|
||||
onChange={(value) => {
|
||||
handleChange("url", value);
|
||||
}}
|
||||
value={inputs.url}
|
||||
placeholder={placeholders["navigation"]["url"]}
|
||||
className="nopan text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex gap-2">
|
||||
<Label className="text-xs text-slate-300">Prompt</Label>
|
||||
<HelpTooltip
|
||||
content={helpTooltips["navigation"]["navigationGoal"]}
|
||||
return (
|
||||
<Flippable facing={facing} preserveFrontsideHeight={true}>
|
||||
<div>
|
||||
<Handle
|
||||
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}
|
||||
editable={editable}
|
||||
disabled={elideFromDebugging}
|
||||
nodeId={id}
|
||||
totpIdentifier={inputs.totpIdentifier}
|
||||
totpUrl={inputs.totpVerificationUrl}
|
||||
type={type}
|
||||
/>
|
||||
<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={helpTooltips["navigation"]["url"]} />
|
||||
</div>
|
||||
{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["navigation"]["url"]}
|
||||
className="nopan text-xs"
|
||||
/>
|
||||
</div>
|
||||
<WorkflowBlockInputTextarea
|
||||
nodeId={id}
|
||||
onChange={(value) => {
|
||||
handleChange("navigationGoal", value);
|
||||
}}
|
||||
value={inputs.navigationGoal}
|
||||
placeholder={placeholders["navigation"]["navigationGoal"]}
|
||||
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: Try to phrase your prompt as a goal with an explicit
|
||||
completion criteria. While executing, Skyvern will take as many
|
||||
actions as necessary to accomplish the goal. Use words like
|
||||
"Complete" or "Terminate" to help Skyvern identify when it's
|
||||
finished or when it should give up.
|
||||
<div className="space-y-2">
|
||||
<div className="flex gap-2">
|
||||
<Label className="text-xs text-slate-300">
|
||||
Navigation Goal
|
||||
</Label>
|
||||
<HelpTooltip
|
||||
content={helpTooltips["navigation"]["navigationGoal"]}
|
||||
/>
|
||||
</div>
|
||||
<WorkflowBlockInputTextarea
|
||||
nodeId={id}
|
||||
onChange={(value) => {
|
||||
handleChange("navigationGoal", value);
|
||||
}}
|
||||
value={inputs.navigationGoal}
|
||||
placeholder={placeholders["navigation"]["navigationGoal"]}
|
||||
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: Try to phrase your prompt as a goal with an explicit
|
||||
completion criteria. While executing, Skyvern will take as many
|
||||
actions as necessary to accomplish the goal. Use words like
|
||||
"Complete" or "Terminate" to help Skyvern identify when it's
|
||||
finished or when it should give up.
|
||||
</div>
|
||||
</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">
|
||||
<ParametersMultiSelect
|
||||
availableOutputParameters={outputParameterKeys}
|
||||
parameters={data.parameterKeys}
|
||||
onParametersChange={(parameterKeys) => {
|
||||
updateNodeData(id, { parameterKeys });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<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["navigation"]["maxStepsOverride"]}
|
||||
<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">
|
||||
<ParametersMultiSelect
|
||||
availableOutputParameters={outputParameterKeys}
|
||||
parameters={data.parameterKeys}
|
||||
onParametersChange={(parameterKeys) => {
|
||||
updateNodeData(id, { parameterKeys });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder={placeholders["navigation"]["maxStepsOverride"]}
|
||||
<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"
|
||||
min="0"
|
||||
value={inputs.maxStepsOverride ?? ""}
|
||||
onChange={(event) => {
|
||||
const value =
|
||||
event.target.value === ""
|
||||
? null
|
||||
: Number(event.target.value);
|
||||
handleChange("maxStepsOverride", value);
|
||||
value={inputs.model}
|
||||
onChange={(value) => {
|
||||
handleChange("model", value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex gap-2">
|
||||
<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>
|
||||
<HelpTooltip
|
||||
content={helpTooltips["navigation"]["errorCodeMapping"]}
|
||||
content={helpTooltips["navigation"]["maxStepsOverride"]}
|
||||
/>
|
||||
</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["navigation"][
|
||||
"includeActionHistoryInVerification"
|
||||
]
|
||||
<Input
|
||||
type="number"
|
||||
placeholder={
|
||||
placeholders["navigation"]["maxStepsOverride"]
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-52">
|
||||
<Switch
|
||||
checked={inputs.includeActionHistoryInVerification}
|
||||
onCheckedChange={(checked) => {
|
||||
handleChange(
|
||||
"includeActionHistoryInVerification",
|
||||
checked,
|
||||
);
|
||||
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>
|
||||
<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["navigation"]["continueOnFailure"]}
|
||||
/>
|
||||
<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["navigation"]["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>
|
||||
<div className="w-52">
|
||||
<Switch
|
||||
checked={inputs.continueOnFailure}
|
||||
onCheckedChange={(checked) => {
|
||||
handleChange("continueOnFailure", checked);
|
||||
<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["navigation"][
|
||||
"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["navigation"]["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["navigation"]["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["navigation"]["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["navigation"]["fileSuffix"]}
|
||||
/>
|
||||
</div>
|
||||
<WorkflowBlockInput
|
||||
nodeId={id}
|
||||
type="text"
|
||||
placeholder={placeholders["navigation"]["downloadSuffix"]}
|
||||
className="nopan w-52 text-xs"
|
||||
value={inputs.downloadSuffix ?? ""}
|
||||
onChange={(value) => {
|
||||
handleChange("downloadSuffix", value);
|
||||
}}
|
||||
/>
|
||||
</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["navigation"]["cacheActions"]}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-52">
|
||||
<Switch
|
||||
checked={inputs.cacheActions}
|
||||
onCheckedChange={(checked) => {
|
||||
handleChange("cacheActions", checked);
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<div className="flex gap-2">
|
||||
<Label className="text-xs text-slate-300">
|
||||
2FA Identifier
|
||||
</Label>
|
||||
<HelpTooltip
|
||||
content={helpTooltips["navigation"]["totpIdentifier"]}
|
||||
/>
|
||||
</div>
|
||||
<WorkflowBlockInputTextarea
|
||||
nodeId={id}
|
||||
onChange={(value) => {
|
||||
handleChange("totpIdentifier", value);
|
||||
}}
|
||||
value={inputs.totpIdentifier ?? ""}
|
||||
placeholder={placeholders["navigation"]["totpIdentifier"]}
|
||||
className="nopan text-xs"
|
||||
/>
|
||||
</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["navigation"]["completeOnDownload"]}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-52">
|
||||
<Switch
|
||||
checked={inputs.allowDownloads}
|
||||
onCheckedChange={(checked) => {
|
||||
handleChange("allowDownloads", checked);
|
||||
<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 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["navigation"]["fileSuffix"]}
|
||||
/>
|
||||
</div>
|
||||
<WorkflowBlockInput
|
||||
nodeId={id}
|
||||
type="text"
|
||||
placeholder={placeholders["navigation"]["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["navigation"]["totpIdentifier"]}
|
||||
/>
|
||||
</div>
|
||||
<WorkflowBlockInputTextarea
|
||||
nodeId={id}
|
||||
onChange={(value) => {
|
||||
handleChange("totpIdentifier", value);
|
||||
}}
|
||||
value={inputs.totpIdentifier ?? ""}
|
||||
placeholder={placeholders["navigation"]["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>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BlockCodeEditor blockLabel={label} blockType={type} script={script} />
|
||||
</Flippable>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,14 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { DotsHorizontalIcon } from "@radix-ui/react-icons";
|
||||
import { OrgWalled } from "@/components/Orgwalled";
|
||||
|
||||
type Props = {
|
||||
onDelete: () => void;
|
||||
onShowScript?: () => void;
|
||||
};
|
||||
|
||||
function NodeActionMenu({ onDelete }: Props) {
|
||||
function NodeActionMenu({ onDelete, onShowScript }: Props) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -28,6 +30,17 @@ function NodeActionMenu({ onDelete }: Props) {
|
||||
>
|
||||
Delete Block
|
||||
</DropdownMenuItem>
|
||||
<OrgWalled className="p-0">
|
||||
{onShowScript && (
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
onShowScript();
|
||||
}}
|
||||
>
|
||||
Show Script
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgWalled>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||
|
||||
import { useNodeLabelChangeHandler } from "@/routes/workflows/hooks/useLabelChangeHandler";
|
||||
import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback";
|
||||
import { useToggleScriptForNodeCallback } from "@/routes/workflows/hooks/useToggleScriptForNodeCallback";
|
||||
import { useDebugSessionQuery } from "@/routes/workflows/hooks/useDebugSessionQuery";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
import {
|
||||
@@ -145,6 +146,7 @@ function NodeHeader({
|
||||
});
|
||||
const blockTitle = workflowBlockTitle[type];
|
||||
const deleteNodeCallback = useDeleteNodeCallback();
|
||||
const toggleScriptForNodeCallback = useToggleScriptForNodeCallback();
|
||||
const credentialGetter = useCredentialGetter();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -411,6 +413,9 @@ function NodeHeader({
|
||||
onDelete={() => {
|
||||
deleteNodeCallback(nodeId);
|
||||
}}
|
||||
onShowScript={() =>
|
||||
toggleScriptForNodeCallback({ id: nodeId, show: true })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ export type NodeBaseData = {
|
||||
continueOnFailure: boolean;
|
||||
editable: boolean;
|
||||
model: WorkflowModel | null;
|
||||
showCode?: boolean;
|
||||
};
|
||||
|
||||
export const errorMappingExampleValue = {
|
||||
|
||||
Reference in New Issue
Block a user