mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-28 04:24:51 +02:00
[eric] canvas: suspend hook back under the file cap, visibility hook and geometry split out
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import type { BrowserCardPosition } from '@/shared/state/dashboardLayoutSlice';
|
||||
|
||||
export interface Viewport {
|
||||
panX: number;
|
||||
panY: number;
|
||||
zoom: number;
|
||||
vpW: number;
|
||||
vpH: number;
|
||||
}
|
||||
|
||||
export function cardIntersectsViewport(card: BrowserCardPosition, vp: Viewport, marginPx: number): boolean {
|
||||
const m = marginPx / vp.zoom;
|
||||
const vx = -vp.panX / vp.zoom - m;
|
||||
const vy = -vp.panY / vp.zoom - m;
|
||||
const vw = vp.vpW / vp.zoom + 2 * m;
|
||||
const vh = vp.vpH / vp.zoom + 2 * m;
|
||||
return card.x < vx + vw && card.x + card.width > vx && card.y < vy + vh && card.y + card.height > vy;
|
||||
}
|
||||
|
||||
export function distFromCenter(card: BrowserCardPosition, vp: Viewport): number {
|
||||
const cx = (-vp.panX + vp.vpW / 2) / vp.zoom;
|
||||
const cy = (-vp.panY + vp.vpH / 2) / vp.zoom;
|
||||
const dx = card.x + card.width / 2 - cx;
|
||||
const dy = card.y + card.height / 2 - cy;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
// Arc's tab-archiving tradeoff: back after a long absence pays a page reload, back quickly pays nothing.
|
||||
const APP_HIDDEN_SUSPEND_MS = 10 * 60 * 1000;
|
||||
|
||||
/** True once the app has been hidden for 10 minutes straight; the suspend loop parks every idle webview then. */
|
||||
export function useAppHidden(enabled: boolean): boolean {
|
||||
const [appHidden, setAppHidden] = useState(false);
|
||||
useEffect(() => {
|
||||
if (!enabled) return undefined;
|
||||
let t: ReturnType<typeof setTimeout> | null = null;
|
||||
const onVis = (): void => {
|
||||
if (t) { clearTimeout(t); t = null; }
|
||||
if (document.hidden) t = setTimeout(() => setAppHidden(true), APP_HIDDEN_SUSPEND_MS);
|
||||
else setAppHidden(false);
|
||||
};
|
||||
document.addEventListener('visibilitychange', onVis);
|
||||
onVis();
|
||||
return () => { document.removeEventListener('visibilitychange', onVis); if (t) clearTimeout(t); };
|
||||
}, [enabled]);
|
||||
return appHidden;
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import { getActivity, isAnyBrowserBusy } from '@/shared/browserCommandHandler';
|
||||
import { isKeepAliveBrowser } from '@/shared/browserFocus';
|
||||
import { captureTabCapsule } from '@/shared/browserStateCapsule';
|
||||
import { getMinimizedShot } from '../../desktop/minimizedShots';
|
||||
import { useAppHidden } from './useAppHidden';
|
||||
import { cardIntersectsViewport, distFromCenter, type Viewport } from './suspendGeometry';
|
||||
|
||||
const isElectron = typeof navigator !== 'undefined' && navigator.userAgent.includes('Electron');
|
||||
|
||||
@@ -23,27 +25,6 @@ const SNAPSHOT_MAX_W = 1024;
|
||||
const RESUME_MIN_CARD_PX = 220;
|
||||
// Hard ceiling on simultaneous live webviews; past it the farthest-from-center non-agent card gets parked, so heavy pages degrade gracefully instead of OOMing.
|
||||
const MAX_LIVE_WEBVIEWS = 8;
|
||||
// App hidden this long = the user left; park every idle renderer (working agents keep theirs) so an
|
||||
// all-day-open OpenSwarm stops holding gigabytes it isn't showing. Arc's tab-archiving tradeoff:
|
||||
// coming back after a long absence pays a page reload, coming back quickly pays nothing.
|
||||
const APP_HIDDEN_SUSPEND_MS = 10 * 60 * 1000;
|
||||
|
||||
interface Viewport {
|
||||
panX: number;
|
||||
panY: number;
|
||||
zoom: number;
|
||||
vpW: number;
|
||||
vpH: number;
|
||||
}
|
||||
|
||||
function cardIntersectsViewport(card: BrowserCardPosition, vp: Viewport, marginPx: number): boolean {
|
||||
const m = marginPx / vp.zoom;
|
||||
const vx = -vp.panX / vp.zoom - m;
|
||||
const vy = -vp.panY / vp.zoom - m;
|
||||
const vw = vp.vpW / vp.zoom + 2 * m;
|
||||
const vh = vp.vpH / vp.zoom + 2 * m;
|
||||
return card.x < vx + vw && card.x + card.width > vx && card.y < vy + vh && card.y + card.height > vy;
|
||||
}
|
||||
|
||||
// Grace after terminal so an agent whose status blips completed->running between back-to-back turns can't lose its browser in the gap.
|
||||
const WORKING_GRACE_MS = 20_000;
|
||||
@@ -132,19 +113,8 @@ export function useWebviewSuspend(
|
||||
const prevMinimizedRef = useRef<Record<string, boolean>>({});
|
||||
const vpRef = useRef<Viewport>({ panX, panY, zoom, vpW: 1200, vpH: 800 });
|
||||
|
||||
const [appHidden, setAppHidden] = useState(false);
|
||||
useEffect(() => {
|
||||
if (!isElectron) return;
|
||||
let t: ReturnType<typeof setTimeout> | null = null;
|
||||
const onVis = (): void => {
|
||||
if (t) { clearTimeout(t); t = null; }
|
||||
if (document.hidden) t = setTimeout(() => setAppHidden(true), APP_HIDDEN_SUSPEND_MS);
|
||||
else setAppHidden(false);
|
||||
};
|
||||
document.addEventListener('visibilitychange', onVis);
|
||||
onVis();
|
||||
return () => { document.removeEventListener('visibilitychange', onVis); if (t) clearTimeout(t); };
|
||||
}, []);
|
||||
// Hidden long enough = the user left; park every idle renderer, working agents keep theirs.
|
||||
const appHidden = useAppHidden(isElectron);
|
||||
|
||||
// Window resize changes the viewport without touching pan/zoom/cards; tick so the evaluation below reruns, or a shrunken window never suspends anything.
|
||||
const [resizeTick, setResizeTick] = useState(0);
|
||||
@@ -239,13 +209,6 @@ export function useWebviewSuspend(
|
||||
}, [browserCards, suspended, minimized, panX, panY, zoom, viewportRef, dispatch, resizeTick, appHidden]);
|
||||
}
|
||||
|
||||
function distFromCenter(card: BrowserCardPosition, vp: Viewport): number {
|
||||
const cx = (-vp.panX + vp.vpW / 2) / vp.zoom;
|
||||
const cy = (-vp.panY + vp.vpH / 2) / vp.zoom;
|
||||
const dx = card.x + card.width / 2 - cx;
|
||||
const dy = card.y + card.height / 2 - cy;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
// capturePage on an already-off-screen webview can HANG forever (Electron 42/Viz stops producing frames for unpainted guests), and one hung await used to wedge the whole suspend pass, silently disabling suspension for every card. Bound it hard.
|
||||
const CAPTURE_TIMEOUT_MS = 1500;
|
||||
|
||||
Reference in New Issue
Block a user