Jon/sky 5838 launch the power restart feature for all users with a cooldown (#3133)

This commit is contained in:
Jonathan Dobson
2025-08-07 17:13:02 -04:00
committed by GitHub
parent 4c219b7f05
commit b0e29834eb
4 changed files with 119 additions and 35 deletions

View File

@@ -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>

View File

@@ -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 && (

View 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 };

View File

@@ -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>
); );
} }