Jon/sky 5838 launch the power restart feature for all users with a cooldown (#3133)
This commit is contained in:
@@ -8,7 +8,7 @@ import type {
|
|||||||
WorkflowRunStatusApiResponse,
|
WorkflowRunStatusApiResponse,
|
||||||
} from "@/api/types";
|
} from "@/api/types";
|
||||||
import { Button } from "@/components/ui/button";
|
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 { toast } from "@/components/ui/use-toast";
|
||||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
import { statusIsNotFinalized } from "@/routes/tasks/types";
|
import { statusIsNotFinalized } from "@/routes/tasks/types";
|
||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
} from "@/util/env";
|
} from "@/util/env";
|
||||||
import { cn } from "@/util/utils";
|
import { cn } from "@/util/utils";
|
||||||
|
|
||||||
|
import { RotateThrough } from "./RotateThrough";
|
||||||
import "./browser-stream.css";
|
import "./browser-stream.css";
|
||||||
|
|
||||||
interface CommandTakeControl {
|
interface CommandTakeControl {
|
||||||
@@ -368,8 +369,18 @@ function BrowserStream({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isVncConnected && (
|
{!isVncConnected && (
|
||||||
<div className="absolute left-0 top-0 flex h-full w-full items-center justify-center bg-black">
|
<div className="flex h-full w-full flex-col items-center justify-center gap-2 pb-2 pt-4 text-sm text-slate-400">
|
||||||
<Skeleton className="aspect-[16/9] h-auto max-h-full w-full max-w-full rounded-lg object-cover" />
|
<RotateThrough interval={7 * 1000}>
|
||||||
|
<span>Hm, working on the connection...</span>
|
||||||
|
<span>Hang tight, we're almost there...</span>
|
||||||
|
<span>Just a moment...</span>
|
||||||
|
<span>Backpropagating...</span>
|
||||||
|
<span>Attention is all I need...</span>
|
||||||
|
<span>Consulting the manual...</span>
|
||||||
|
<span>Looking for the bat phone...</span>
|
||||||
|
<span>Where's Shu?...</span>
|
||||||
|
</RotateThrough>
|
||||||
|
<AnimatedWave text=".‧₊˚ ⋅ ? ✨ ?★ ‧₊˚ ⋅" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import {
|
|||||||
import { flushSync } from "react-dom";
|
import { flushSync } from "react-dom";
|
||||||
import Draggable from "react-draggable";
|
import Draggable from "react-draggable";
|
||||||
|
|
||||||
import { OrgWalled } from "./Orgwalled";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -628,11 +627,7 @@ function FloatingWindow({
|
|||||||
onClick={toggleMaximized}
|
onClick={toggleMaximized}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showPowerButton && (
|
{showPowerButton && <PowerButton onClick={() => cycle()} />}
|
||||||
<OrgWalled className="flex items-center justify-center">
|
|
||||||
<PowerButton onClick={() => cycle()} />
|
|
||||||
</OrgWalled>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-auto">{title}</div>
|
<div className="ml-auto">{title}</div>
|
||||||
{showReloadButton && (
|
{showReloadButton && (
|
||||||
|
|||||||
56
skyvern-frontend/src/components/RotateThrough.tsx
Normal file
56
skyvern-frontend/src/components/RotateThrough.tsx
Normal file
@@ -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 <div className={className}>{childrenArray[0]}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`transition-all duration-150 ease-in-out ${
|
||||||
|
isAnimating ? "scale-95 opacity-0" : "scale-100 opacity-100"
|
||||||
|
} ${className}`}
|
||||||
|
>
|
||||||
|
{childrenArray[currentIndex]}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { RotateThrough };
|
||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogClose,
|
DialogClose,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
|
||||||
import { toast } from "@/components/ui/use-toast";
|
import { toast } from "@/components/ui/use-toast";
|
||||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
import { useMountEffect } from "@/hooks/useMountEffect";
|
import { useMountEffect } from "@/hooks/useMountEffect";
|
||||||
@@ -35,11 +34,16 @@ import { FlowRenderer } from "./FlowRenderer";
|
|||||||
import { getElements } from "./workflowEditorUtils";
|
import { getElements } from "./workflowEditorUtils";
|
||||||
import { getInitialParameters } from "./utils";
|
import { getInitialParameters } from "./utils";
|
||||||
|
|
||||||
|
const Constants = {
|
||||||
|
NewBrowserCooldown: 30000,
|
||||||
|
} as const;
|
||||||
|
|
||||||
function WorkflowDebugger() {
|
function WorkflowDebugger() {
|
||||||
const { blockLabel, workflowPermanentId } = useParams();
|
const { blockLabel, workflowPermanentId } = useParams();
|
||||||
const [openDialogue, setOpenDialogue] = useState(false);
|
const [openDialogue, setOpenDialogue] = useState(false);
|
||||||
const [activeDebugSession, setActiveDebugSession] =
|
const [activeDebugSession, setActiveDebugSession] =
|
||||||
useState<DebugSessionApiResponse | null>(null);
|
useState<DebugSessionApiResponse | null>(null);
|
||||||
|
const [showPowerButton, setShowPowerButton] = useState(true);
|
||||||
const credentialGetter = useCredentialGetter();
|
const credentialGetter = useCredentialGetter();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [shouldFetchDebugSession, setShouldFetchDebugSession] = useState(false);
|
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({
|
const cycleBrowser = useMutation({
|
||||||
mutationFn: async (id: string) => {
|
mutationFn: async (id: string) => {
|
||||||
const client = await getClient(credentialGetter, "sans-api-v1");
|
const client = await getClient(credentialGetter, "sans-api-v1");
|
||||||
@@ -97,7 +114,7 @@ function WorkflowDebugger() {
|
|||||||
description: "Your browser has been cycled.",
|
description: "Your browser has been cycled.",
|
||||||
});
|
});
|
||||||
|
|
||||||
setOpenDialogue(false);
|
afterCycleBrowser();
|
||||||
},
|
},
|
||||||
onError: (error: AxiosError) => {
|
onError: (error: AxiosError) => {
|
||||||
toast({
|
toast({
|
||||||
@@ -105,11 +122,13 @@ function WorkflowDebugger() {
|
|||||||
title: "Failed to cycle browser",
|
title: "Failed to cycle browser",
|
||||||
description: error.message,
|
description: error.message,
|
||||||
});
|
});
|
||||||
setOpenDialogue(false);
|
|
||||||
|
afterCycleBrowser();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const powerButtonTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -225,29 +244,32 @@ function WorkflowDebugger() {
|
|||||||
/>
|
/>
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
|
|
||||||
{activeDebugSession && (
|
<FloatingWindow
|
||||||
<FloatingWindow
|
title={browserTitle}
|
||||||
title={browserTitle}
|
bounded={false}
|
||||||
bounded={false}
|
initialWidth={512}
|
||||||
initialWidth={512}
|
initialHeight={360}
|
||||||
initialHeight={360}
|
showMaximizeButton={true}
|
||||||
showMaximizeButton={true}
|
showMinimizeButton={true}
|
||||||
showMinimizeButton={true}
|
showPowerButton={blockLabel === undefined && showPowerButton}
|
||||||
showPowerButton={blockLabel === undefined}
|
showReloadButton={true}
|
||||||
showReloadButton={true}
|
// --
|
||||||
// --
|
onCycle={handleOnCycle}
|
||||||
onCycle={handleOnCycle}
|
>
|
||||||
>
|
{activeDebugSession &&
|
||||||
{activeDebugSession && activeDebugSession.browser_session_id ? (
|
activeDebugSession.browser_session_id &&
|
||||||
<BrowserStream
|
!cycleBrowser.isPending ? (
|
||||||
interactive={interactor === "human"}
|
<BrowserStream
|
||||||
browserSessionId={activeDebugSession.browser_session_id}
|
interactive={interactor === "human"}
|
||||||
/>
|
browserSessionId={activeDebugSession.browser_session_id}
|
||||||
) : (
|
/>
|
||||||
<Skeleton className="h-full w-full" />
|
) : (
|
||||||
)}
|
<div className="flex h-full w-full flex-col items-center justify-center gap-2 pb-2 pt-4 text-sm text-slate-400">
|
||||||
</FloatingWindow>
|
Connecting to your browser...
|
||||||
)}
|
<AnimatedWave text=".‧₊˚ ⋅ ✨★ ‧₊˚ ⋅" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</FloatingWindow>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user