add take control/cede control buttons to browser stream view; improve branding yar (#2989)
This commit is contained in:
@@ -1,18 +1,26 @@
|
|||||||
import { Status } from "@/api/types";
|
|
||||||
import { useEffect, useState, useRef, useCallback } from "react";
|
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
|
||||||
import { statusIsNotFinalized } from "@/routes/tasks/types";
|
|
||||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
|
||||||
import { envCredential } from "@/util/env";
|
|
||||||
import { toast } from "@/components/ui/use-toast";
|
|
||||||
import RFB from "@novnc/novnc/lib/rfb.js";
|
import RFB from "@novnc/novnc/lib/rfb.js";
|
||||||
import { environment, wssBaseUrl, newWssBaseUrl } from "@/util/env";
|
import { ExitIcon, HandIcon } from "@radix-ui/react-icons";
|
||||||
import { cn } from "@/util/utils";
|
import { useEffect, useState, useRef, useCallback } from "react";
|
||||||
import { useClientIdStore } from "@/store/useClientIdStore";
|
|
||||||
|
import { Status } from "@/api/types";
|
||||||
import type {
|
import type {
|
||||||
TaskApiResponse,
|
TaskApiResponse,
|
||||||
WorkflowRunStatusApiResponse,
|
WorkflowRunStatusApiResponse,
|
||||||
} from "@/api/types";
|
} from "@/api/types";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { toast } from "@/components/ui/use-toast";
|
||||||
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
|
import { statusIsNotFinalized } from "@/routes/tasks/types";
|
||||||
|
import { useClientIdStore } from "@/store/useClientIdStore";
|
||||||
|
import {
|
||||||
|
envCredential,
|
||||||
|
environment,
|
||||||
|
wssBaseUrl,
|
||||||
|
newWssBaseUrl,
|
||||||
|
} from "@/util/env";
|
||||||
|
import { cn } from "@/util/utils";
|
||||||
|
|
||||||
import "./browser-stream.css";
|
import "./browser-stream.css";
|
||||||
|
|
||||||
interface CommandTakeControl {
|
interface CommandTakeControl {
|
||||||
@@ -28,6 +36,7 @@ type Command = CommandTakeControl | CommandCedeControl;
|
|||||||
type Props = {
|
type Props = {
|
||||||
browserSessionId?: string;
|
browserSessionId?: string;
|
||||||
interactive?: boolean;
|
interactive?: boolean;
|
||||||
|
showControlButtons?: boolean;
|
||||||
task?: {
|
task?: {
|
||||||
run: TaskApiResponse;
|
run: TaskApiResponse;
|
||||||
};
|
};
|
||||||
@@ -41,6 +50,7 @@ type Props = {
|
|||||||
function BrowserStream({
|
function BrowserStream({
|
||||||
browserSessionId = undefined,
|
browserSessionId = undefined,
|
||||||
interactive = true,
|
interactive = true,
|
||||||
|
showControlButtons = undefined,
|
||||||
task = undefined,
|
task = undefined,
|
||||||
workflow = undefined,
|
workflow = undefined,
|
||||||
// --
|
// --
|
||||||
@@ -65,7 +75,7 @@ function BrowserStream({
|
|||||||
} else {
|
} else {
|
||||||
throw new Error("No browser session, task or workflow provided");
|
throw new Error("No browser session, task or workflow provided");
|
||||||
}
|
}
|
||||||
|
const [userIsControlling, setUserIsControlling] = useState(false);
|
||||||
const [commandSocket, setCommandSocket] = useState<WebSocket | null>(null);
|
const [commandSocket, setCommandSocket] = useState<WebSocket | null>(null);
|
||||||
const [vncDisconnectedTrigger, setVncDisconnectedTrigger] = useState(0);
|
const [vncDisconnectedTrigger, setVncDisconnectedTrigger] = useState(0);
|
||||||
const prevVncConnectedRef = useRef<boolean>(false);
|
const prevVncConnectedRef = useRef<boolean>(false);
|
||||||
@@ -312,14 +322,51 @@ function BrowserStream({
|
|||||||
}
|
}
|
||||||
}, [task, workflow]);
|
}, [task, workflow]);
|
||||||
|
|
||||||
|
const theUserIsControlling =
|
||||||
|
userIsControlling || (interactive && !showControlButtons);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("browser-stream", {
|
className={cn("browser-stream", {
|
||||||
"user-is-controlling": interactive,
|
"user-is-controlling": theUserIsControlling,
|
||||||
})}
|
})}
|
||||||
ref={setCanvasContainerRef}
|
ref={setCanvasContainerRef}
|
||||||
>
|
>
|
||||||
{isVncConnected && <div className="overlay" />}
|
{isVncConnected && (
|
||||||
|
<div className="overlay z-10 flex items-center justify-center">
|
||||||
|
{showControlButtons && (
|
||||||
|
<div className="control-buttons pointer-events-none relative flex h-full w-full items-center justify-center">
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setUserIsControlling(true);
|
||||||
|
}}
|
||||||
|
className={cn("control-button pointer-events-auto border", {
|
||||||
|
hide: userIsControlling,
|
||||||
|
})}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<HandIcon className="mr-2 h-4 w-4" />
|
||||||
|
take control
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setUserIsControlling(false);
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
"control-button pointer-events-auto absolute bottom-0 border",
|
||||||
|
{
|
||||||
|
hide: !userIsControlling,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<ExitIcon className="mr-2 h-4 w-4" />
|
||||||
|
cede control
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{!isVncConnected && (
|
{!isVncConnected && (
|
||||||
<div className="absolute left-0 top-0 flex h-full w-full items-center justify-center bg-black">
|
<div className="absolute left-0 top-0 flex h-full w-full items-center justify-center bg-black">
|
||||||
<Skeleton className="aspect-[16/9] h-auto max-h-full w-full max-w-full rounded-lg object-cover" />
|
<Skeleton className="aspect-[16/9] h-auto max-h-full w-full max-w-full rounded-lg object-cover" />
|
||||||
|
|||||||
@@ -73,6 +73,17 @@
|
|||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.browser-stream .control-button {
|
||||||
|
transition: 0.3s all ease-in-out;
|
||||||
|
transform: translateY(0px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-stream .control-button.hide {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translateY(15px);
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes skyvern-anim-fadeIn {
|
@keyframes skyvern-anim-fadeIn {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { BrowserStream } from "@/components/BrowserStream";
|
|
||||||
import { getClient } from "@/api/AxiosClient";
|
|
||||||
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { getClient } from "@/api/AxiosClient";
|
||||||
|
import { BrowserStream } from "@/components/BrowserStream";
|
||||||
|
import { LogoMinimized } from "@/components/LogoMinimized";
|
||||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
|
|
||||||
function BrowserSession() {
|
function BrowserSession() {
|
||||||
@@ -52,8 +53,20 @@ function BrowserSession() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen w-full gap-4 p-6">
|
<div className="h-screen w-full gap-4 p-6">
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full flex-col items-start justify-start gap-2">
|
||||||
<BrowserStream browserSessionId={browserSessionId} />
|
<div className="flex w-full flex-shrink-0 flex-row items-center justify-between rounded-lg border p-4">
|
||||||
|
<div className="flex flex-row items-center justify-start gap-2">
|
||||||
|
<LogoMinimized />
|
||||||
|
<div className="text-xl">browser session</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="min-h-0 w-full flex-1 rounded-lg border p-4">
|
||||||
|
<BrowserStream
|
||||||
|
browserSessionId={browserSessionId}
|
||||||
|
interactive={false}
|
||||||
|
showControlButtons={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user