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... + +
+ )} +
); }