From e5e812ff671f085cb584b2e4476ef9f7378e3c72 Mon Sep 17 00:00:00 2001 From: Jonathan Dobson Date: Thu, 9 Oct 2025 12:58:36 -0400 Subject: [PATCH] improve time-to-render/availability-checking of BrowserStream component (#3667) --- .../src/components/BrowserStream.tsx | 47 ++++++++-- .../src/routes/workflows/editor/Workspace.tsx | 91 ++++++++----------- 2 files changed, 77 insertions(+), 61 deletions(-) diff --git a/skyvern-frontend/src/components/BrowserStream.tsx b/skyvern-frontend/src/components/BrowserStream.tsx index 9d2a2f39..565c2ae3 100644 --- a/skyvern-frontend/src/components/BrowserStream.tsx +++ b/skyvern-frontend/src/components/BrowserStream.tsx @@ -67,8 +67,8 @@ function BrowserStream({ onClose, }: Props) { let showStream: boolean = false; - let runId: string; - let entity: "browserSession" | "task" | "workflow"; + let runId: string | null; + let entity: "browserSession" | "task" | "workflow" | null; if (browserSessionId) { runId = browserSessionId; @@ -84,7 +84,8 @@ function BrowserStream({ showStream = statusIsNotFinalized(workflow.run); entity = "workflow"; } else { - throw new Error("No browser session id, task or workflow provided"); + entity = null; + runId = null; } useQuery({ @@ -120,6 +121,8 @@ function BrowserStream({ const [vncDisconnectedTrigger, setVncDisconnectedTrigger] = useState(0); const prevVncConnectedRef = useRef(false); const [isVncConnected, setIsVncConnected] = useState(false); + const [isCanvasReady, setIsCanvasReady] = useState(false); + const [isReady, setIsReady] = useState(false); const [commandDisconnectedTrigger, setCommandDisconnectedTrigger] = useState(0); const prevCommandConnectedRef = useRef(false); @@ -131,6 +134,7 @@ function BrowserStream({ setCanvasContainer(node); }, []); const rfbRef = useRef(null); + const observerRef = useRef(null); const clientId = useClientIdStore((state) => state.clientId); const credentialGetter = useCredentialGetter(); @@ -154,6 +158,11 @@ function BrowserStream({ return `${params}`; }, [clientId, credentialGetter]); + // browser is ready + useEffect(() => { + setIsReady(isVncConnected && isCanvasReady && hasBrowserSession); + }, [hasBrowserSession, isCanvasReady, isVncConnected]); + // effect for vnc disconnects only useEffect(() => { if (prevVncConnectedRef.current && !isVncConnected) { @@ -218,31 +227,57 @@ function BrowserStream({ throw new Error("Canvas element not found"); } + observerRef.current = new MutationObserver(() => { + const canvasElement = canvasContainer.querySelector("canvas"); + if (canvasElement) { + setIsCanvasReady(true); + observerRef.current?.disconnect(); + } + }); + + observerRef.current.observe(canvasContainer, { + childList: true, + subtree: true, + }); + const rfb = new RFB(canvas, vncUrl); rfb.scaleViewport = true; rfbRef.current = rfb; + const canvasElement = canvasContainer.querySelector("canvas"); + + if (canvasElement) { + setIsCanvasReady(true); + observerRef.current?.disconnect(); + } + rfb.addEventListener("connect", () => { setIsVncConnected(true); }); rfb.addEventListener("disconnect", async (/* e: RfbEvent */) => { setIsVncConnected(false); + setIsCanvasReady(false); }); - // setIsVncConnected(true); // be optimistic + setIsVncConnected(true); // be optimistic } setupVnc(); return () => { + if (observerRef.current) { + observerRef.current.disconnect(); + observerRef.current = null; + } if (rfbRef.current) { rfbRef.current.disconnect(); rfbRef.current = null; } setIsVncConnected(false); + setIsCanvasReady(false); }; }, // cannot include isVncConnected in deps as it will cause infinite loop @@ -402,7 +437,7 @@ function BrowserStream({ )} ref={setCanvasContainerRef} > - {isVncConnected && hasBrowserSession && ( + {isReady && (
{showControlButtons && (
@@ -437,7 +472,7 @@ function BrowserStream({ )}
)} - {!isVncConnected && ( + {!isReady && (
{browserSessionId && !hasBrowserSession ? (
This live browser session is no longer streaming.
diff --git a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx index d431a93a..b5f5288b 100644 --- a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx @@ -706,16 +706,8 @@ function Workspace({ version2: WorkflowVersion, mode: "visual" | "json" = "visual", ) => { - console.log( - `${mode === "visual" ? "Visual" : "JSON"} comparison between versions:`, - version1.version, - "and", - version2.version, - ); - // Implement visual drawer comparison if (mode === "visual") { - console.log("Opening visual comparison panel..."); // Keep history panel active but add comparison data setWorkflowPanelState({ active: true, @@ -731,15 +723,13 @@ function Workspace({ // TODO: Implement JSON diff comparison if (mode === "json") { // This will open a JSON diff view - console.log("Opening JSON diff view..."); + console.warn("[Not Implemented] opening JSON diff view..."); // Future: setJsonDiffOpen(true); // Future: setJsonDiffVersions({ version1, version2 }); } }; const handleSelectState = (selectedVersion: WorkflowVersion) => { - console.log("Loading version into main editor:", selectedVersion.version); - // Close panels setWorkflowPanelState({ active: false, @@ -1269,51 +1259,42 @@ function Workspace({ {/* browser & timeline */}
{/* VNC browser */} - {!activeDebugSession || - (activeDebugSession.vnc_streaming_supported && ( -
-
- {activeDebugSession && - activeDebugSession.browser_session_id && - !cycleBrowser.isPending ? ( - - ) : ( -
- Connecting to your browser... - -
- )} -
-
-
- Live Browser -
- {showBreakoutButton && ( - breakout()} /> - )} -
- {showPowerButton && ( - cycle()} /> - )} - reload()} - /> -
-
+ {(!activeDebugSession || + activeDebugSession.vnc_streaming_supported) && ( +
+
+
- ))} + +
+ )} {/* Screenshot browser} */} {activeDebugSession &&