From d82eba77b6ce333f41c0d19c1eee667b98cec05e Mon Sep 17 00:00:00 2001 From: Jonathan Dobson Date: Fri, 12 Sep 2025 16:13:05 -0400 Subject: [PATCH] multiple UI fixes/updates (#3422) --- .../src/components/BrowserStream.tsx | 11 +- skyvern-frontend/src/components/Splitter.tsx | 35 +-- skyvern-frontend/src/router.tsx | 5 +- .../src/routes/workflows/editor/Workspace.tsx | 209 +++++++++--------- .../workflowRun/WorkflowRunStream.tsx | 30 ++- 5 files changed, 168 insertions(+), 122 deletions(-) diff --git a/skyvern-frontend/src/components/BrowserStream.tsx b/skyvern-frontend/src/components/BrowserStream.tsx index c69ac7e1..624923fe 100644 --- a/skyvern-frontend/src/components/BrowserStream.tsx +++ b/skyvern-frontend/src/components/BrowserStream.tsx @@ -343,9 +343,12 @@ function BrowserStream({ return (
{isVncConnected && ( @@ -384,7 +387,7 @@ function BrowserStream({
)} {!isVncConnected && ( -
+
Hm, working on the connection... Hang tight, we're almost there... diff --git a/skyvern-frontend/src/components/Splitter.tsx b/skyvern-frontend/src/components/Splitter.tsx index 74fc7f2e..b0b52670 100644 --- a/skyvern-frontend/src/components/Splitter.tsx +++ b/skyvern-frontend/src/components/Splitter.tsx @@ -1,7 +1,7 @@ import { useRef, useState, RefObject } from "react"; -import { useMountEffect } from "@/hooks/useMountEffect"; import { cn } from "@/util/utils"; import { useOnChange } from "@/hooks/useOnChange"; +import { useMountEffect } from "@/hooks/useMountEffect"; function Handle({ direction, @@ -341,21 +341,30 @@ function Splitter({ const [isDragging, setIsDragging] = useState(false); useMountEffect(() => { - if (containerRef.current) { - const newPosition = normalizeUnitsToPercent( - containerRef, - direction, - firstSizingTarget, - firstSizing, - storageKey, - ); + // 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, + ); - 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/router.tsx b/skyvern-frontend/src/router.tsx index 72edd503..9cf9c2a4 100644 --- a/skyvern-frontend/src/router.tsx +++ b/skyvern-frontend/src/router.tsx @@ -13,6 +13,7 @@ import { TaskDetails } from "./routes/tasks/detail/TaskDetails"; import { TaskParameters } from "./routes/tasks/detail/TaskParameters"; import { TaskRecording } from "./routes/tasks/detail/TaskRecording"; import { TasksPage } from "./routes/tasks/list/TasksPage"; +import { Debugger } from "@/routes/workflows/debugger/Debugger"; import { WorkflowPage } from "./routes/workflows/WorkflowPage"; import { WorkflowRun } from "./routes/workflows/WorkflowRun"; import { WorkflowRunParameters } from "./routes/workflows/WorkflowRunParameters"; @@ -110,11 +111,11 @@ const router = createBrowserRouter([ }, { path: "debug", - element: , + element: , }, { path: ":workflowRunId/:blockLabel/debug", - element: , + element: , }, { path: "edit", diff --git a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx index 27b7f2ae..18e1ffd4 100644 --- a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx @@ -1,5 +1,5 @@ import { AxiosError } from "axios"; -import { useEffect, useRef, useState, useMemo } from "react"; +import { useEffect, useRef, useState } from "react"; import { nanoid } from "nanoid"; import { ChevronRightIcon, @@ -17,6 +17,7 @@ import { useCredentialGetter } from "@/hooks/useCredentialGetter"; import { useMountEffect } from "@/hooks/useMountEffect"; import { useDebugSessionQuery } from "../hooks/useDebugSessionQuery"; import { useBlockScriptsQuery } from "@/routes/workflows/hooks/useBlockScriptsQuery"; +import { WorkflowRunStream } from "@/routes/workflows/workflowRun/WorkflowRunStream"; import { useCacheKeyValuesQuery } from "../hooks/useCacheKeyValuesQuery"; import { useBlockScriptStore } from "@/store/BlockScriptStore"; import { useSidebarStore } from "@/store/SidebarStore"; @@ -137,12 +138,6 @@ function Workspace({ const blockScriptStore = useBlockScriptStore(); const cacheKey = workflow?.cache_key ?? ""; - const enableDebugBrowser = useMemo(() => { - return ( - showBrowser && (activeDebugSession?.vnc_streaming_supported ?? false) - ); - }, [showBrowser, activeDebugSession?.vnc_streaming_supported]); - const [cacheKeyValue, setCacheKeyValue] = useState( cacheKey === "" ? "" @@ -215,10 +210,10 @@ function Workspace({ const hasLoopBlock = nodes.some((node) => node.type === "loop"); const hasHttpBlock = nodes.some((node) => node.type === "http_request"); const workflowWidth = hasHttpBlock - ? "35.1rem" + ? "39rem" : hasLoopBlock - ? "31.25rem" - : "30rem"; + ? "34.25rem" + : "34rem"; /** * Open a new tab (not window) with the browser session URL. @@ -705,7 +700,7 @@ function Workspace({
{/* infinite canvas and sub panels when not in debug mode */} - {!enableDebugBrowser && ( + {!showBrowser && (
{/* infinite canvas */} )} {workflowPanelState.content === "parameters" && ( -
+
)} {workflowPanelState.content === "nodeLibrary" && ( -
+
{ addNode(props); @@ -768,8 +763,51 @@ function Workspace({
)} - {/* infinite canvas, sub panels, browser, and timeline when in debug mode */} - {enableDebugBrowser && ( + {/* sub panels when in debug mode */} + {showBrowser && workflowPanelState.active && ( +
+ {workflowPanelState.content === "cacheKeyValues" && ( + { + setToDeleteCacheKeyValue(cacheKeyValue); + setOpenConfirmCacheKeyValueDeleteDialogue(true); + }} + onPaginate={(page) => { + setPage(page); + }} + onSelect={(cacheKeyValue) => { + setCacheKeyValue(cacheKeyValue); + setCacheKeyValueFilter(""); + closeWorkflowPanel(); + }} + /> + )} + {workflowPanelState.content === "parameters" && ( + + )} + {workflowPanelState.content === "nodeLibrary" && ( + { + addNode(props); + }} + /> + )} +
+ )} + + {/* infinite canvas, browser, and timeline when in debug mode */} + {showBrowser && (
- {/* browser & timeline & sub-panels in debug mode */} + {/* browser & timeline */}
- {/* sub panels */} - {workflowPanelState.active && ( -
- {workflowPanelState.content === "cacheKeyValues" && ( - { - setToDeleteCacheKeyValue(cacheKeyValue); - setOpenConfirmCacheKeyValueDeleteDialogue(true); - }} - onPaginate={(page) => { - setPage(page); - }} - onSelect={(cacheKeyValue) => { - setCacheKeyValue(cacheKeyValue); - setCacheKeyValueFilter(""); - closeWorkflowPanel(); - }} - /> - )} - {workflowPanelState.content === "parameters" && ( - - )} - {workflowPanelState.content === "nodeLibrary" && ( - { - addNode(props); - }} - /> - )} -
- )} - - {/* browser & timeline */}
- {/* browser */} -
-
- {activeDebugSession && - activeDebugSession.browser_session_id && - !cycleBrowser.isPending ? ( - - ) : ( -
- Connecting to your browser... - + {/* VNC browser */} + {!activeDebugSession || + (activeDebugSession.vnc_streaming_supported && ( +
+
+ {activeDebugSession && + activeDebugSession.browser_session_id && + !cycleBrowser.isPending ? ( + + ) : ( +
+ Connecting to your browser... + +
+ )}
- )} -
-
-
- Live Browser +
+
+ Live Browser +
+ {showBreakoutButton && ( + breakout()} /> + )} +
+ {showPowerButton && ( + cycle()} /> + )} + reload()} + /> +
+
- {showBreakoutButton && ( - breakout()} /> - )} -
- {showPowerButton && ( - cycle()} /> - )} - reload()} - /> + ))} + + {/* Screenshot browser} */} + {activeDebugSession && + !activeDebugSession.vnc_streaming_supported && ( +
+
+ +
-
-
+ )} {/* timeline */}
(""); - const showStream = workflowRun && statusIsNotFinalized(workflowRun); + const showStream = + alwaysShowStream || (workflowRun && statusIsNotFinalized(workflowRun)); const credentialGetter = useCredentialGetter(); const { workflowRunId, workflowPermanentId } = useParams(); const queryClient = useQueryClient(); @@ -149,6 +155,26 @@ function WorkflowRunStream() {
); } + + if (alwaysShowStream) { + if (streamImgSrc?.length > 0) { + return ( +
+ +
+ ); + } + + return ( +
+ Waiting for stream... +
+ ); + } + return null; }