diff --git a/skyvern-frontend/src/components/BrowserStream.tsx b/skyvern-frontend/src/components/BrowserStream.tsx
index 617a15ef..9ee8ae4c 100644
--- a/skyvern-frontend/src/components/BrowserStream.tsx
+++ b/skyvern-frontend/src/components/BrowserStream.tsx
@@ -8,7 +8,7 @@ import type {
WorkflowRunStatusApiResponse,
} from "@/api/types";
import { Button } from "@/components/ui/button";
-import { Skeleton } from "@/components/ui/skeleton";
+import { AnimatedWave } from "@/components/AnimatedWave";
import { toast } from "@/components/ui/use-toast";
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
import { statusIsNotFinalized } from "@/routes/tasks/types";
@@ -21,6 +21,7 @@ import {
} from "@/util/env";
import { cn } from "@/util/utils";
+import { RotateThrough } from "./RotateThrough";
import "./browser-stream.css";
interface CommandTakeControl {
@@ -368,8 +369,18 @@ function BrowserStream({
)}
{!isVncConnected && (
-
-
+
+
+ Hm, working on the connection...
+ Hang tight, we're almost there...
+ Just a moment...
+ Backpropagating...
+ Attention is all I need...
+ Consulting the manual...
+ Looking for the bat phone...
+ Where's Shu?...
+
+
)}
diff --git a/skyvern-frontend/src/components/FloatingWindow.tsx b/skyvern-frontend/src/components/FloatingWindow.tsx
index 563a5d49..37988744 100644
--- a/skyvern-frontend/src/components/FloatingWindow.tsx
+++ b/skyvern-frontend/src/components/FloatingWindow.tsx
@@ -18,7 +18,6 @@ import {
import { flushSync } from "react-dom";
import Draggable from "react-draggable";
-import { OrgWalled } from "./Orgwalled";
import {
Tooltip,
TooltipContent,
@@ -628,11 +627,7 @@ function FloatingWindow({
onClick={toggleMaximized}
/>
)}
- {showPowerButton && (
-
- cycle()} />
-
- )}
+ {showPowerButton && cycle()} />}
{title}
{showReloadButton && (
diff --git a/skyvern-frontend/src/components/RotateThrough.tsx b/skyvern-frontend/src/components/RotateThrough.tsx
new file mode 100644
index 00000000..3e64ebef
--- /dev/null
+++ b/skyvern-frontend/src/components/RotateThrough.tsx
@@ -0,0 +1,56 @@
+import { ReactNode, useEffect, useState } from "react";
+
+interface RotateThroughProps {
+ children: ReactNode[];
+ interval: number; // milliseconds
+ className?: string;
+}
+
+function RotateThrough({
+ children,
+ interval,
+ className = "",
+}: RotateThroughProps) {
+ const [currentIndex, setCurrentIndex] = useState(0);
+ const [isAnimating, setIsAnimating] = useState(false);
+
+ const childrenArray = Array.isArray(children) ? children : [children];
+
+ useEffect(() => {
+ if (childrenArray.length <= 1) return;
+
+ const timer = setInterval(() => {
+ setIsAnimating(true);
+
+ // After a short animation delay, change the content
+ setTimeout(() => {
+ setCurrentIndex((prevIndex) =>
+ prevIndex === childrenArray.length - 1 ? 0 : prevIndex + 1,
+ );
+ setIsAnimating(false);
+ }, 150); // Animation duration
+ }, interval);
+
+ return () => clearInterval(timer);
+ }, [childrenArray.length, interval]);
+
+ if (childrenArray.length === 0) {
+ return null;
+ }
+
+ if (childrenArray.length === 1) {
+ return {childrenArray[0]}
;
+ }
+
+ return (
+
+ {childrenArray[currentIndex]}
+
+ );
+}
+
+export { RotateThrough };
diff --git a/skyvern-frontend/src/routes/workflows/editor/WorkflowDebugger.tsx b/skyvern-frontend/src/routes/workflows/editor/WorkflowDebugger.tsx
index 0482d360..12a83f37 100644
--- a/skyvern-frontend/src/routes/workflows/editor/WorkflowDebugger.tsx
+++ b/skyvern-frontend/src/routes/workflows/editor/WorkflowDebugger.tsx
@@ -20,7 +20,6 @@ import {
DialogTitle,
DialogClose,
} from "@/components/ui/dialog";
-import { Skeleton } from "@/components/ui/skeleton";
import { toast } from "@/components/ui/use-toast";
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
import { useMountEffect } from "@/hooks/useMountEffect";
@@ -35,11 +34,16 @@ import { FlowRenderer } from "./FlowRenderer";
import { getElements } from "./workflowEditorUtils";
import { getInitialParameters } from "./utils";
+const Constants = {
+ NewBrowserCooldown: 30000,
+} as const;
+
function WorkflowDebugger() {
const { blockLabel, workflowPermanentId } = useParams();
const [openDialogue, setOpenDialogue] = useState(false);
const [activeDebugSession, setActiveDebugSession] =
useState(null);
+ const [showPowerButton, setShowPowerButton] = useState(true);
const credentialGetter = useCredentialGetter();
const queryClient = useQueryClient();
const [shouldFetchDebugSession, setShouldFetchDebugSession] = useState(false);
@@ -78,6 +82,19 @@ function WorkflowDebugger() {
}
});
+ const afterCycleBrowser = () => {
+ setOpenDialogue(false);
+ setShowPowerButton(false);
+
+ if (powerButtonTimeoutRef.current) {
+ clearTimeout(powerButtonTimeoutRef.current);
+ }
+
+ powerButtonTimeoutRef.current = setTimeout(() => {
+ setShowPowerButton(true);
+ }, Constants.NewBrowserCooldown);
+ };
+
const cycleBrowser = useMutation({
mutationFn: async (id: string) => {
const client = await getClient(credentialGetter, "sans-api-v1");
@@ -97,7 +114,7 @@ function WorkflowDebugger() {
description: "Your browser has been cycled.",
});
- setOpenDialogue(false);
+ afterCycleBrowser();
},
onError: (error: AxiosError) => {
toast({
@@ -105,11 +122,13 @@ function WorkflowDebugger() {
title: "Failed to cycle browser",
description: error.message,
});
- setOpenDialogue(false);
+
+ afterCycleBrowser();
},
});
const intervalRef = useRef(null);
+ const powerButtonTimeoutRef = useRef(null);
useEffect(() => {
if (
@@ -225,29 +244,32 @@ function WorkflowDebugger() {
/>
- {activeDebugSession && (
-
- {activeDebugSession && activeDebugSession.browser_session_id ? (
-
- ) : (
-
- )}
-
- )}
+
+ {activeDebugSession &&
+ activeDebugSession.browser_session_id &&
+ !cycleBrowser.isPending ? (
+
+ ) : (
+
+ Connecting to your browser...
+
+
+ )}
+
);
}