From f2146080ce277781efcea33d46561974c17657cd Mon Sep 17 00:00:00 2001 From: Jonathan Dobson Date: Tue, 16 Sep 2025 16:32:15 -0400 Subject: [PATCH] Jon/sky 6375 alter how show all code works (#3445) --- skyvern-frontend/src/components/Splitter.tsx | 33 ++-- .../workflows/editor/WorkflowHeader.tsx | 126 ++++++++------- .../src/routes/workflows/editor/Workspace.tsx | 144 +++++++++++++++--- .../editor/nodes/StartNode/StartNode.tsx | 47 ++---- .../src/routes/workflows/utils.ts | 47 ++++++ .../workflows/workflowRun/WorkflowRunCode.tsx | 48 +----- 6 files changed, 273 insertions(+), 172 deletions(-) diff --git a/skyvern-frontend/src/components/Splitter.tsx b/skyvern-frontend/src/components/Splitter.tsx index b0b52670..b7c3155a 100644 --- a/skyvern-frontend/src/components/Splitter.tsx +++ b/skyvern-frontend/src/components/Splitter.tsx @@ -341,30 +341,21 @@ function Splitter({ const [isDragging, setIsDragging] = useState(false); useMountEffect(() => { - // small delay here, to allow for arbitrary layout thrashing to settle; - // otherwise we have to rely on an observer for the container size, and - // resetting whenever the container resizes it likely incorrect behaviour - setTimeout(() => { - if (containerRef.current) { - const newPosition = normalizeUnitsToPercent( - containerRef, - direction, - firstSizingTarget, - firstSizing, - storageKey, - ); + if (containerRef.current) { + const newPosition = normalizeUnitsToPercent( + containerRef, + direction, + firstSizingTarget, + firstSizing, + storageKey, + ); - setSplitPosition(newPosition); + setSplitPosition(newPosition); - if (storageKey) { - setStoredSizing( - firstSizingTarget, - storageKey, - newPosition.toString(), - ); - } + if (storageKey) { + setStoredSizing(firstSizingTarget, storageKey, newPosition.toString()); } - }, 100); + } }); useOnChange(isDragging, (newValue, oldValue) => { diff --git a/skyvern-frontend/src/routes/workflows/editor/WorkflowHeader.tsx b/skyvern-frontend/src/routes/workflows/editor/WorkflowHeader.tsx index 40c7a6ea..0183020f 100644 --- a/skyvern-frontend/src/routes/workflows/editor/WorkflowHeader.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/WorkflowHeader.tsx @@ -1,6 +1,7 @@ import { ChevronDownIcon, ChevronUpIcon, + CodeIcon, CopyIcon, PlayIcon, ReloadIcon, @@ -41,12 +42,14 @@ type Props = { cacheKeyValuesPanelOpen: boolean; parametersPanelOpen: boolean; saving: boolean; + showAllCode: boolean; workflow: WorkflowApiResponse; onCacheKeyValueAccept: (cacheKeyValue: string | null) => void; onCacheKeyValuesBlurred: (cacheKeyValue: string | null) => void; onCacheKeyValuesFilter: (cacheKeyValue: string) => void; onCacheKeyValuesKeydown: (e: React.KeyboardEvent) => void; onParametersClick: () => void; + onShowAllCodeClick?: () => void; onCacheKeyValuesClick: () => void; onSave: () => void; onRun?: () => void; @@ -58,12 +61,14 @@ function WorkflowHeader({ cacheKeyValuesPanelOpen, parametersPanelOpen, saving, + showAllCode, workflow, onCacheKeyValueAccept, onCacheKeyValuesBlurred, onCacheKeyValuesFilter, onCacheKeyValuesKeydown, onParametersClick, + onShowAllCodeClick, onCacheKeyValuesClick, onSave, onRun, @@ -87,6 +92,10 @@ function WorkflowHeader({ input: useRef(null), }; + const handleShowAllCode = () => { + onShowAllCodeClick?.(); + }; + useEffect(() => { if (cacheKeyValue === chosenCacheKeyValue) { return; @@ -125,62 +134,75 @@ function WorkflowHeader({
{user && workflow.generate_script && ( // (cacheKeyValues?.total_count ?? 0) > 0 && ( -
- { - setChosenCacheKeyValue(e.target.value); - onCacheKeyValuesFilter(e.target.value); - }} - onMouseDown={() => { - if (!cacheKeyValuesPanelOpen) { - onCacheKeyValuesClick(); - } - }} - onKeyDown={(e) => { - if (e.key === "Enter") { - const numFiltered = cacheKeyValues?.values?.length ?? 0; - - if (numFiltered === 1) { - const first = cacheKeyValues?.values?.[0]; - if (first) { - setChosenCacheKeyValue(first); - onCacheKeyValueAccept(first); - } - return; + <> + {debugStore.isDebugMode && ( + + )} +
+ { + setChosenCacheKeyValue(e.target.value); + onCacheKeyValuesFilter(e.target.value); + }} + onMouseDown={() => { + if (!cacheKeyValuesPanelOpen) { + onCacheKeyValuesClick(); } + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + const numFiltered = cacheKeyValues?.values?.length ?? 0; - setChosenCacheKeyValue(chosenCacheKeyValue); - onCacheKeyValueAccept(chosenCacheKeyValue); - } - onCacheKeyValuesKeydown(e); - }} - placeholder="Code Key Value" - value={chosenCacheKeyValue ?? undefined} - onBlur={(e) => { - onCacheKeyValuesBlurred(e.target.value); - setChosenCacheKeyValue(e.target.value); - }} - /> - {cacheKeyValuesPanelOpen ? ( - - ) : ( - { - dom.input.current?.focus(); - onCacheKeyValuesClick(); + if (numFiltered === 1) { + const first = cacheKeyValues?.values?.[0]; + if (first) { + setChosenCacheKeyValue(first); + onCacheKeyValueAccept(first); + } + return; + } + + setChosenCacheKeyValue(chosenCacheKeyValue); + onCacheKeyValueAccept(chosenCacheKeyValue); + } + onCacheKeyValuesKeydown(e); + }} + placeholder="Code Key Value" + value={chosenCacheKeyValue ?? undefined} + onBlur={(e) => { + onCacheKeyValuesBlurred(e.target.value); + setChosenCacheKeyValue(e.target.value); }} /> - )} -
+ {cacheKeyValuesPanelOpen ? ( + + ) : ( + { + dom.input.current?.focus(); + onCacheKeyValuesClick(); + }} + /> + )} +
+ )} {isGlobalWorkflow ? ( + ); +} + function Workspace({ initialNodes, initialEdges, @@ -146,6 +169,15 @@ function Workspace({ : constructCacheKeyValue({ codeKey: cacheKey, workflow }), ); + const [showAllCode, setShowAllCode] = useState(false); + const [leftSideLayoutMode, setLeftSideLayoutMode] = useState< + "single" | "side-by-side" + >("single"); + + const dom: Dom = { + splitLeft: useRef(null), + }; + useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") { @@ -400,6 +432,32 @@ function Workspace({ }; }, [debugSession, shouldFetchDebugSession, workflowPermanentId, queryClient]); + useEffect(() => { + const splitLeft = dom.splitLeft.current; + + if (!splitLeft) { + return; + } + + const parent = splitLeft.parentElement; + + if (!parent) { + return; + } + + const observer = new ResizeObserver(() => { + setLeftSideLayoutMode( + parent.offsetWidth < 1100 ? "single" : "side-by-side", + ); + }); + + observer.observe(parent); + + return () => { + observer.disconnect(); + }; + }, [dom.splitLeft]); + function doLayout(nodes: Array, edges: Array) { const layoutedElements = layout(nodes, edges); setNodes(layoutedElements.nodes); @@ -508,6 +566,9 @@ function Workspace({ } } + const orderedBlockLabels = getOrderedBlockLabels(workflow); + const code = getCode(orderedBlockLabels, blockScripts).join(""); + return (
{/* cycle browser dialog */} @@ -631,6 +692,7 @@ function Workspace({ workflowPanelState.active && workflowPanelState.content === "parameters" } + showAllCode={showAllCode} workflow={workflow} onCacheKeyValueAccept={(v) => { setCacheKeyValue(v ?? ""); @@ -696,6 +758,9 @@ function Workspace({ onRun={() => { closeWorkflowPanel(); }} + onShowAllCodeClick={() => { + setShowAllCode(!showAllCode); + }} />
@@ -806,7 +871,7 @@ function Workspace({
)} - {/* infinite canvas, browser, and timeline when in debug mode */} + {/* code, infinite canvas, browser, and timeline when in debug mode */} {showBrowser && (
setContainerResizeTrigger((prev) => prev + 1)} > - {/* infinite canvas */} -
- + {/* code and infinite canvas */} +
+
+ {/* code */} +
+
+
+ +
+ +
+
+ {/* infinite canvas */} +
+ +
+
{/* browser & timeline */} 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 ff61bc42..f63a0a38 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/StartNode/StartNode.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/StartNode/StartNode.tsx @@ -1,13 +1,6 @@ import { getClient } from "@/api/AxiosClient"; import { Handle, Node, NodeProps, Position, useReactFlow } from "@xyflow/react"; import type { StartNode } from "./types"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { Button } from "@/components/ui/button"; import { Accordion, AccordionContent, @@ -42,7 +35,6 @@ import { useRerender } from "@/hooks/useRerender"; import { useBlockScriptStore } from "@/store/BlockScriptStore"; import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor"; import { cn } from "@/util/utils"; -import { LightningBoltIcon } from "@radix-ui/react-icons"; function StartNode({ id, data }: NodeProps) { const workflowSettingsStore = useWorkflowSettingsStore(); @@ -115,19 +107,20 @@ function StartNode({ id, data }: NodeProps) { ); } - function showAllScripts() { - for (const node of reactFlowInstance.getNodes()) { - const label = node.data.label; + // NOTE(jdo): keeping for reference; we seem to revert stuff all the time + // function showAllScripts() { + // for (const node of reactFlowInstance.getNodes()) { + // const label = node.data.label; - label && - nodeIsFlippable(node) && - typeof label === "string" && - toggleScriptForNodeCallback({ - label, - show: true, - }); - } - } + // label && + // nodeIsFlippable(node) && + // typeof label === "string" && + // toggleScriptForNodeCallback({ + // label, + // show: true, + // }); + // } + // } function hideAllScripts() { for (const node of reactFlowInstance.getNodes()) { @@ -160,20 +153,6 @@ function StartNode({ id, data }: NodeProps) { )} >
-
-
- -
-
Start
; @@ -77,3 +78,49 @@ export const formatDuration = (duration: Duration): string => { return `${duration.second}s`; } }; + +export const getOrderedBlockLabels = (workflow?: WorkflowApiResponse) => { + if (!workflow) { + return []; + } + + const blockLabels = workflow.workflow_definition.blocks.map( + (block) => block.label, + ); + + return blockLabels; +}; + +const getCommentForBlockWithoutCode = (blockLabel: string) => { + return ` + # block '${blockLabel}' code goes here +`; +}; + +export const getCode = ( + orderedBlockLabels: string[], + blockScripts?: { + [blockName: string]: string; + }, +): string[] => { + const blockCode: string[] = []; + const startBlockCode = blockScripts?.__start_block__; + + if (startBlockCode) { + blockCode.push(startBlockCode); + } + + for (const blockLabel of orderedBlockLabels) { + const code = blockScripts?.[blockLabel]; + + if (!code) { + blockCode.push(getCommentForBlockWithoutCode(blockLabel)); + continue; + } + + blockCode.push(`${code} +`); + } + + return blockCode; +}; diff --git a/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowRunCode.tsx b/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowRunCode.tsx index 314d4a5b..f94ca143 100644 --- a/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowRunCode.tsx +++ b/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowRunCode.tsx @@ -18,58 +18,12 @@ import { useCacheKeyValuesQuery } from "@/routes/workflows/hooks/useCacheKeyValu import { useWorkflowQuery } from "@/routes/workflows/hooks/useWorkflowQuery"; import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery"; import { constructCacheKeyValue } from "@/routes/workflows/editor/utils"; -import { WorkflowApiResponse } from "@/routes/workflows/types/workflowTypes"; +import { getCode, getOrderedBlockLabels } from "@/routes/workflows/utils"; interface Props { showCacheKeyValueSelector?: boolean; } -const getOrderedBlockLabels = (workflow?: WorkflowApiResponse) => { - if (!workflow) { - return []; - } - - const blockLabels = workflow.workflow_definition.blocks.map( - (block) => block.label, - ); - - return blockLabels; -}; - -const getCommentForBlockWithoutCode = (blockLabel: string) => { - return ` - # If the "Generate Code" option is turned on for this workflow when it runs, AI will execute block '${blockLabel}', and generate code for it. -`; -}; - -const getCode = ( - orderedBlockLabels: string[], - blockScripts?: { - [blockName: string]: string; - }, -): string[] => { - const blockCode: string[] = []; - const startBlockCode = blockScripts?.__start_block__; - - if (startBlockCode) { - blockCode.push(startBlockCode); - } - - for (const blockLabel of orderedBlockLabels) { - const code = blockScripts?.[blockLabel]; - - if (!code) { - blockCode.push(getCommentForBlockWithoutCode(blockLabel)); - continue; - } - - blockCode.push(`${code} -`); - } - - return blockCode; -}; - function WorkflowRunCode(props?: Props) { const showCacheKeyValueSelector = props?.showCacheKeyValueSelector ?? false; const queryClient = useQueryClient();