improve time-to-render/availability-checking of BrowserStream component (#3667)
This commit is contained in:
@@ -67,8 +67,8 @@ function BrowserStream({
|
|||||||
onClose,
|
onClose,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
let showStream: boolean = false;
|
let showStream: boolean = false;
|
||||||
let runId: string;
|
let runId: string | null;
|
||||||
let entity: "browserSession" | "task" | "workflow";
|
let entity: "browserSession" | "task" | "workflow" | null;
|
||||||
|
|
||||||
if (browserSessionId) {
|
if (browserSessionId) {
|
||||||
runId = browserSessionId;
|
runId = browserSessionId;
|
||||||
@@ -84,7 +84,8 @@ function BrowserStream({
|
|||||||
showStream = statusIsNotFinalized(workflow.run);
|
showStream = statusIsNotFinalized(workflow.run);
|
||||||
entity = "workflow";
|
entity = "workflow";
|
||||||
} else {
|
} else {
|
||||||
throw new Error("No browser session id, task or workflow provided");
|
entity = null;
|
||||||
|
runId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
useQuery({
|
useQuery({
|
||||||
@@ -120,6 +121,8 @@ function BrowserStream({
|
|||||||
const [vncDisconnectedTrigger, setVncDisconnectedTrigger] = useState(0);
|
const [vncDisconnectedTrigger, setVncDisconnectedTrigger] = useState(0);
|
||||||
const prevVncConnectedRef = useRef<boolean>(false);
|
const prevVncConnectedRef = useRef<boolean>(false);
|
||||||
const [isVncConnected, setIsVncConnected] = useState<boolean>(false);
|
const [isVncConnected, setIsVncConnected] = useState<boolean>(false);
|
||||||
|
const [isCanvasReady, setIsCanvasReady] = useState<boolean>(false);
|
||||||
|
const [isReady, setIsReady] = useState(false);
|
||||||
const [commandDisconnectedTrigger, setCommandDisconnectedTrigger] =
|
const [commandDisconnectedTrigger, setCommandDisconnectedTrigger] =
|
||||||
useState(0);
|
useState(0);
|
||||||
const prevCommandConnectedRef = useRef<boolean>(false);
|
const prevCommandConnectedRef = useRef<boolean>(false);
|
||||||
@@ -131,6 +134,7 @@ function BrowserStream({
|
|||||||
setCanvasContainer(node);
|
setCanvasContainer(node);
|
||||||
}, []);
|
}, []);
|
||||||
const rfbRef = useRef<RFB | null>(null);
|
const rfbRef = useRef<RFB | null>(null);
|
||||||
|
const observerRef = useRef<MutationObserver | null>(null);
|
||||||
const clientId = useClientIdStore((state) => state.clientId);
|
const clientId = useClientIdStore((state) => state.clientId);
|
||||||
const credentialGetter = useCredentialGetter();
|
const credentialGetter = useCredentialGetter();
|
||||||
|
|
||||||
@@ -154,6 +158,11 @@ function BrowserStream({
|
|||||||
return `${params}`;
|
return `${params}`;
|
||||||
}, [clientId, credentialGetter]);
|
}, [clientId, credentialGetter]);
|
||||||
|
|
||||||
|
// browser is ready
|
||||||
|
useEffect(() => {
|
||||||
|
setIsReady(isVncConnected && isCanvasReady && hasBrowserSession);
|
||||||
|
}, [hasBrowserSession, isCanvasReady, isVncConnected]);
|
||||||
|
|
||||||
// effect for vnc disconnects only
|
// effect for vnc disconnects only
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (prevVncConnectedRef.current && !isVncConnected) {
|
if (prevVncConnectedRef.current && !isVncConnected) {
|
||||||
@@ -218,31 +227,57 @@ function BrowserStream({
|
|||||||
throw new Error("Canvas element not found");
|
throw new Error("Canvas element not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
observerRef.current = new MutationObserver(() => {
|
||||||
|
const canvasElement = canvasContainer.querySelector("canvas");
|
||||||
|
if (canvasElement) {
|
||||||
|
setIsCanvasReady(true);
|
||||||
|
observerRef.current?.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observerRef.current.observe(canvasContainer, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
|
||||||
const rfb = new RFB(canvas, vncUrl);
|
const rfb = new RFB(canvas, vncUrl);
|
||||||
|
|
||||||
rfb.scaleViewport = true;
|
rfb.scaleViewport = true;
|
||||||
|
|
||||||
rfbRef.current = rfb;
|
rfbRef.current = rfb;
|
||||||
|
|
||||||
|
const canvasElement = canvasContainer.querySelector("canvas");
|
||||||
|
|
||||||
|
if (canvasElement) {
|
||||||
|
setIsCanvasReady(true);
|
||||||
|
observerRef.current?.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
rfb.addEventListener("connect", () => {
|
rfb.addEventListener("connect", () => {
|
||||||
setIsVncConnected(true);
|
setIsVncConnected(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
rfb.addEventListener("disconnect", async (/* e: RfbEvent */) => {
|
rfb.addEventListener("disconnect", async (/* e: RfbEvent */) => {
|
||||||
setIsVncConnected(false);
|
setIsVncConnected(false);
|
||||||
|
setIsCanvasReady(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// setIsVncConnected(true); // be optimistic
|
setIsVncConnected(true); // be optimistic
|
||||||
}
|
}
|
||||||
|
|
||||||
setupVnc();
|
setupVnc();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
if (observerRef.current) {
|
||||||
|
observerRef.current.disconnect();
|
||||||
|
observerRef.current = null;
|
||||||
|
}
|
||||||
if (rfbRef.current) {
|
if (rfbRef.current) {
|
||||||
rfbRef.current.disconnect();
|
rfbRef.current.disconnect();
|
||||||
rfbRef.current = null;
|
rfbRef.current = null;
|
||||||
}
|
}
|
||||||
setIsVncConnected(false);
|
setIsVncConnected(false);
|
||||||
|
setIsCanvasReady(false);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
// cannot include isVncConnected in deps as it will cause infinite loop
|
// cannot include isVncConnected in deps as it will cause infinite loop
|
||||||
@@ -402,7 +437,7 @@ function BrowserStream({
|
|||||||
)}
|
)}
|
||||||
ref={setCanvasContainerRef}
|
ref={setCanvasContainerRef}
|
||||||
>
|
>
|
||||||
{isVncConnected && hasBrowserSession && (
|
{isReady && (
|
||||||
<div className="overlay z-10 flex items-center justify-center overflow-hidden">
|
<div className="overlay z-10 flex items-center justify-center overflow-hidden">
|
||||||
{showControlButtons && (
|
{showControlButtons && (
|
||||||
<div className="control-buttons pointer-events-none relative flex h-full w-full items-center justify-center">
|
<div className="control-buttons pointer-events-none relative flex h-full w-full items-center justify-center">
|
||||||
@@ -437,7 +472,7 @@ function BrowserStream({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isVncConnected && (
|
{!isReady && (
|
||||||
<div className="absolute left-0 top-1/2 flex aspect-video max-h-full w-full -translate-y-1/2 flex-col items-center justify-center gap-2 rounded-md border border-slate-800 text-sm text-slate-400">
|
<div className="absolute left-0 top-1/2 flex aspect-video max-h-full w-full -translate-y-1/2 flex-col items-center justify-center gap-2 rounded-md border border-slate-800 text-sm text-slate-400">
|
||||||
{browserSessionId && !hasBrowserSession ? (
|
{browserSessionId && !hasBrowserSession ? (
|
||||||
<div>This live browser session is no longer streaming.</div>
|
<div>This live browser session is no longer streaming.</div>
|
||||||
|
|||||||
@@ -706,16 +706,8 @@ function Workspace({
|
|||||||
version2: WorkflowVersion,
|
version2: WorkflowVersion,
|
||||||
mode: "visual" | "json" = "visual",
|
mode: "visual" | "json" = "visual",
|
||||||
) => {
|
) => {
|
||||||
console.log(
|
|
||||||
`${mode === "visual" ? "Visual" : "JSON"} comparison between versions:`,
|
|
||||||
version1.version,
|
|
||||||
"and",
|
|
||||||
version2.version,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Implement visual drawer comparison
|
// Implement visual drawer comparison
|
||||||
if (mode === "visual") {
|
if (mode === "visual") {
|
||||||
console.log("Opening visual comparison panel...");
|
|
||||||
// Keep history panel active but add comparison data
|
// Keep history panel active but add comparison data
|
||||||
setWorkflowPanelState({
|
setWorkflowPanelState({
|
||||||
active: true,
|
active: true,
|
||||||
@@ -731,15 +723,13 @@ function Workspace({
|
|||||||
// TODO: Implement JSON diff comparison
|
// TODO: Implement JSON diff comparison
|
||||||
if (mode === "json") {
|
if (mode === "json") {
|
||||||
// This will open a JSON diff view
|
// This will open a JSON diff view
|
||||||
console.log("Opening JSON diff view...");
|
console.warn("[Not Implemented] opening JSON diff view...");
|
||||||
// Future: setJsonDiffOpen(true);
|
// Future: setJsonDiffOpen(true);
|
||||||
// Future: setJsonDiffVersions({ version1, version2 });
|
// Future: setJsonDiffVersions({ version1, version2 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectState = (selectedVersion: WorkflowVersion) => {
|
const handleSelectState = (selectedVersion: WorkflowVersion) => {
|
||||||
console.log("Loading version into main editor:", selectedVersion.version);
|
|
||||||
|
|
||||||
// Close panels
|
// Close panels
|
||||||
setWorkflowPanelState({
|
setWorkflowPanelState({
|
||||||
active: false,
|
active: false,
|
||||||
@@ -1269,51 +1259,42 @@ function Workspace({
|
|||||||
{/* browser & timeline */}
|
{/* browser & timeline */}
|
||||||
<div className="flex h-[calc(100%_-_8rem)] w-full gap-6">
|
<div className="flex h-[calc(100%_-_8rem)] w-full gap-6">
|
||||||
{/* VNC browser */}
|
{/* VNC browser */}
|
||||||
{!activeDebugSession ||
|
{(!activeDebugSession ||
|
||||||
(activeDebugSession.vnc_streaming_supported && (
|
activeDebugSession.vnc_streaming_supported) && (
|
||||||
<div className="skyvern-vnc-browser flex h-full w-[calc(100%_-_6rem)] flex-1 flex-col items-center justify-center">
|
<div className="skyvern-vnc-browser flex h-full w-[calc(100%_-_6rem)] flex-1 flex-col items-center justify-center">
|
||||||
<div key={reloadKey} className="w-full flex-1">
|
<div key={reloadKey} className="w-full flex-1">
|
||||||
{activeDebugSession &&
|
<BrowserStream
|
||||||
activeDebugSession.browser_session_id &&
|
interactive={true}
|
||||||
!cycleBrowser.isPending ? (
|
browserSessionId={
|
||||||
<BrowserStream
|
activeDebugSession?.browser_session_id
|
||||||
interactive={true}
|
}
|
||||||
browserSessionId={
|
showControlButtons={true}
|
||||||
activeDebugSession.browser_session_id
|
resizeTrigger={windowResizeTrigger}
|
||||||
}
|
/>
|
||||||
showControlButtons={true}
|
|
||||||
resizeTrigger={windowResizeTrigger}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="flex aspect-video w-full flex-col items-center justify-center gap-2 rounded-md border border-slate-800 pb-2 pt-4 text-sm text-slate-400">
|
|
||||||
Connecting to your browser...
|
|
||||||
<AnimatedWave text=".‧₊˚ ⋅ ✨★ ‧₊˚ ⋅" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<footer className="flex h-[2rem] w-full items-center justify-start gap-4">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<GlobeIcon /> Live Browser
|
|
||||||
</div>
|
|
||||||
{showBreakoutButton && (
|
|
||||||
<BreakoutButton onClick={() => breakout()} />
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className={cn("ml-auto flex items-center gap-2", {
|
|
||||||
"mr-16": !blockLabel,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{showPowerButton && (
|
|
||||||
<PowerButton onClick={() => cycle()} />
|
|
||||||
)}
|
|
||||||
<ReloadButton
|
|
||||||
isReloading={isReloading}
|
|
||||||
onClick={() => reload()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
<footer className="flex h-[2rem] w-full items-center justify-start gap-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<GlobeIcon /> Live Browser
|
||||||
|
</div>
|
||||||
|
{showBreakoutButton && (
|
||||||
|
<BreakoutButton onClick={() => breakout()} />
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={cn("ml-auto flex items-center gap-2", {
|
||||||
|
"mr-16": !blockLabel,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{showPowerButton && (
|
||||||
|
<PowerButton onClick={() => cycle()} />
|
||||||
|
)}
|
||||||
|
<ReloadButton
|
||||||
|
isReloading={isReloading}
|
||||||
|
onClick={() => reload()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Screenshot browser} */}
|
{/* Screenshot browser} */}
|
||||||
{activeDebugSession &&
|
{activeDebugSession &&
|
||||||
|
|||||||
Reference in New Issue
Block a user