diff --git a/skyvern-frontend/src/routes/workflows/components/BlockCodeEditor.tsx b/skyvern-frontend/src/routes/workflows/components/BlockCodeEditor.tsx index 094139ee..a77ff349 100644 --- a/skyvern-frontend/src/routes/workflows/components/BlockCodeEditor.tsx +++ b/skyvern-frontend/src/routes/workflows/components/BlockCodeEditor.tsx @@ -16,15 +16,22 @@ function BlockCodeEditor({ blockLabel, blockType, script, + title, onClick, + onExit, }: { blockLabel: string; - blockType: WorkflowBlockType; + blockType?: WorkflowBlockType; script: string | undefined; + title?: string; onClick?: (e: React.MouseEvent) => void; + /** + * Return `false` to cancel the exit. + */ + onExit?: () => boolean; }) { const [searchParams] = useSearchParams(); - const blockTitle = workflowBlockTitle[blockType]; + const blockTitle = blockType ? workflowBlockTitle[blockType] : title; const toggleScriptForNodeCallback = useToggleScriptForNodeCallback(); const cacheKeyValue = searchParams.get("cache-key-value"); @@ -53,38 +60,51 @@ function BlockCodeEditor({ }} >
-
-
- -
- code -
-
-
- {blockLabel} -
- {blockTitle} -
- + {blockType ? ( +
+
+ +
+ code +
+
+ +
+ {blockLabel} +
+ {blockTitle} +
+ +
+ + {cacheKeyValue === "" || !cacheKeyValue + ? "(none)" + : cacheKeyValue} +
- - {cacheKeyValue === "" || !cacheKeyValue - ? "(none)" - : cacheKeyValue} -
-
+ ) : ( +
+ {title ?? blockLabel} +
+ )}
{ - toggleScriptForNodeCallback({ - label: blockLabel, - show: false, - }); + if (onExit) { + const result = onExit(); + + if (result !== false) { + toggleScriptForNodeCallback({ + label: blockLabel, + show: false, + }); + } + } }} className="size-5 cursor-pointer" /> diff --git a/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx b/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx index 61345b59..077636b9 100644 --- a/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx @@ -459,7 +459,7 @@ function FlowRenderer({ }) { if (id) { const node = nodes.find((node) => node.id === id); - if (!node || !isWorkflowBlockNode(node)) { + if (!node) { return; } @@ -469,7 +469,7 @@ function FlowRenderer({ (node) => "label" in node.data && node.data.label === label, ); - if (!node || !isWorkflowBlockNode(node)) { + if (!node) { return; } diff --git a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx index 78dd7a11..26eb2c72 100644 --- a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx @@ -417,6 +417,8 @@ function Workspace({ { withWorkflowSettings: false, editable: true, + label: "__start_block__", + showCode: false, }, id, ), diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/NodeActionMenu.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/NodeActionMenu.tsx index 3b0a119c..5be4994a 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/NodeActionMenu.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/NodeActionMenu.tsx @@ -10,16 +10,24 @@ import { DotsHorizontalIcon } from "@radix-ui/react-icons"; import { OrgWalled } from "@/components/Orgwalled"; type Props = { + isDeleteable?: boolean; isScriptable?: boolean; - onDelete: () => void; + showScriptText?: string; + onDelete?: () => void; onShowScript?: () => void; }; function NodeActionMenu({ + isDeleteable = true, isScriptable = false, + showScriptText, onDelete, onShowScript, }: Props) { + if (!isDeleteable && !isScriptable) { + return null; + } + return ( @@ -28,13 +36,15 @@ function NodeActionMenu({ Block Actions - { - onDelete(); - }} - > - Delete Block - + {isDeleteable && ( + { + onDelete?.(); + }} + > + Delete Block + + )} {isScriptable && ( {onShowScript && ( @@ -43,7 +53,7 @@ function NodeActionMenu({ onShowScript(); }} > - Show Script + {showScriptText ?? "Show Script"} )} diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/StartNode/StartNode.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/StartNode/StartNode.tsx index 0339bbb6..7a62bce7 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/StartNode/StartNode.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/StartNode/StartNode.tsx @@ -1,5 +1,5 @@ import { getClient } from "@/api/AxiosClient"; -import { Handle, NodeProps, Position, useReactFlow } from "@xyflow/react"; +import { Handle, Node, NodeProps, Position, useReactFlow } from "@xyflow/react"; import type { StartNode } from "./types"; import { Accordion, @@ -25,12 +25,25 @@ import { MAX_SCREENSHOT_SCROLLS_DEFAULT } from "../Taskv2Node/types"; import { KeyValueInput } from "@/components/KeyValueInput"; import { OrgWalled } from "@/components/Orgwalled"; import { placeholders } from "@/routes/workflows/editor/helpContent"; +import { NodeActionMenu } from "@/routes/workflows/editor/nodes/NodeActionMenu"; import { useWorkflowSettingsStore } from "@/store/WorkflowSettingsStore"; +import { + scriptableWorkflowBlockTypes, + type WorkflowBlockType, +} from "@/routes/workflows/types/workflowTypes"; +// import { useToggleScriptForNodeCallback } from "@/routes/workflows/hooks/useToggleScriptForNodeCallback"; + +import { Flippable } from "@/components/Flippable"; +import { useRerender } from "@/hooks/useRerender"; +import { useBlockScriptStore } from "@/store/BlockScriptStore"; +import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor"; function StartNode({ id, data }: NodeProps) { const workflowSettingsStore = useWorkflowSettingsStore(); const credentialGetter = useCredentialGetter(); const { updateNodeData } = useReactFlow(); + // const toggleScriptForNodeCallback = useToggleScriptForNodeCallback(); + const reactFlowInstance = useReactFlow(); const { data: availableModels } = useQuery({ queryKey: ["models"], @@ -66,6 +79,15 @@ function StartNode({ id, data }: NodeProps) { scriptCacheKey: data.withWorkflowSettings ? data.scriptCacheKey : null, }); + const [facing, setFacing] = useState<"front" | "back">("front"); + const blockScriptStore = useBlockScriptStore(); + const script = blockScriptStore.scripts.__start_block__; + const rerender = useRerender({ prefix: "accordion" }); + + useEffect(() => { + setFacing(data.showCode ? "back" : "front"); + }, [data.showCode]); + useEffect(() => { workflowSettingsStore.setWorkflowSettings(inputs); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -79,148 +101,217 @@ function StartNode({ id, data }: NodeProps) { updateNodeData(id, { [key]: value }); } + function nodeIsFlippable(node: Node) { + return ( + scriptableWorkflowBlockTypes.has(node.type as WorkflowBlockType) || + node.type === "start" + ); + } + + function showAllScripts() { + reactFlowInstance.setNodes((nodes) => { + return nodes.map((node) => { + if (nodeIsFlippable(node)) { + return { + ...node, + data: { + ...node.data, + showCode: true, + }, + }; + } + return node; + }); + }); + } + + function hideAllScripts() { + reactFlowInstance.setNodes((nodes) => { + return nodes.map((node) => { + if (nodeIsFlippable(node)) { + return { + ...node, + data: { + ...node.data, + showCode: false, + }, + }; + } + return node; + }); + }); + } + if (data.withWorkflowSettings) { return ( -
- -
-
-
Start
- - - - - Workflow Settings - - -
-
- { - handleChange("model", value); - }} - /> -
-
-
- - + +
+ +
+
+
+
+
+ +
+
+
+
Start
+ + rerender.bump()} + > + + + Workflow Settings + + +
+
+ { + handleChange("model", value); + }} + />
- { - handleChange( - "webhookCallbackUrl", - event.target.value, - ); - }} - /> -
-
-
- - +
+
+ + +
+ { + handleChange( + "webhookCallbackUrl", + event.target.value, + ); + }} + />
- { - handleChange("proxyLocation", value); - }} - /> -
- +
+
+ + +
+ { + handleChange("proxyLocation", value); + }} + /> +
+ +
+
+ + + { + handleChange("useScriptCache", value); + }} + /> +
+
+ {inputs.useScriptCache && ( +
+
+ +
+ { + const v = value.length ? value : null; + handleChange("scriptCacheKey", v); + }} + value={inputs.scriptCacheKey ?? ""} + placeholder={placeholders["scripts"]["scriptKey"]} + className="nopan text-xs" + /> +
+ )} +
- - + + { - handleChange("useScriptCache", value); + handleChange("persistBrowserSession", value); }} />
- {inputs.useScriptCache && ( -
-
- -
- { - const v = value.length ? value : null; - handleChange("scriptCacheKey", v); - }} - value={inputs.scriptCacheKey ?? ""} - placeholder={placeholders["scripts"]["scriptKey"]} - className="nopan text-xs" +
+
+ + +
+ + handleChange("extraHttpHeaders", val) + } + addButtonText="Add Header" + /> +
+
+
+ +
- )} - -
-
- - - { - handleChange("persistBrowserSession", value); + { + const value = + event.target.value === "" + ? null + : Number(event.target.value); + + handleChange("maxScreenshotScrolls", value); }} />
-
-
- - -
- - handleChange("extraHttpHeaders", val) - } - addButtonText="Add Header" - /> -
-
-
- - -
- { - const value = - event.target.value === "" - ? null - : Number(event.target.value); - - handleChange("maxScreenshotScrolls", value); - }} - /> -
-
- - - + + + +
-
+ + { + hideAllScripts(); + return false; + }} + /> + ); } diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/StartNode/types.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/StartNode/types.ts index f4b7f733..5a15bc53 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/StartNode/types.ts +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/StartNode/types.ts @@ -14,11 +14,15 @@ export type WorkflowStartNodeData = { editable: boolean; useScriptCache: boolean; scriptCacheKey: string | null; + label: "__start_block__"; + showCode: boolean; }; export type OtherStartNodeData = { withWorkflowSettings: false; editable: boolean; + label: "__start_block__"; + showCode: boolean; }; export type StartNodeData = WorkflowStartNodeData | OtherStartNodeData; diff --git a/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts b/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts index cf612731..834689a3 100644 --- a/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts +++ b/skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts @@ -703,6 +703,8 @@ function getElements( editable, useScriptCache: settings.useScriptCache, scriptCacheKey: settings.scriptCacheKey, + label: "__start_block__", + showCode: false, }), ); @@ -733,6 +735,8 @@ function getElements( { withWorkflowSettings: false, editable, + label: "__start_block__", + showCode: false, }, block.id, ),