Jon/sky 5820 make browser task block flippable with code (#3165)

This commit is contained in:
Jonathan Dobson
2025-08-11 19:57:08 -04:00
committed by GitHub
parent e5106124e3
commit 039fce0bb3
16 changed files with 663 additions and 339 deletions

View File

@@ -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>
</>
);

View File

@@ -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);

View File

@@ -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">

View File

@@ -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>
);
}

View File

@@ -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>
);

View File

@@ -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>

View File

@@ -7,6 +7,7 @@ export type NodeBaseData = {
continueOnFailure: boolean;
editable: boolean;
model: WorkflowModel | null;
showCode?: boolean;
};
export const errorMappingExampleValue = {