performance updates

This commit is contained in:
Arnav
2026-04-25 19:32:45 -05:00
parent e3a72fe3f9
commit 1b0d34b026
5 changed files with 120 additions and 41 deletions
@@ -170,7 +170,7 @@ const AppShell: React.FC = () => {
return;
}
}
dispatch(addBrowserCard({ url }));
dispatch(addBrowserCard({ url, viewportWidth: window.innerWidth }));
} else {
dispatch(setPendingBrowserUrl(url));
const lastId = (window as any).__openswarm_last_dashboard_id as string | undefined;
@@ -45,7 +45,7 @@ const SelectionOverlay: React.FC<Props> = ({ overlay, dragRect, dragPreview = []
return;
}
const updateRects = () => {
const measureRects = () => {
const rects: PersistentRect[] = [];
for (const sel of semanticEls) {
try {
@@ -65,13 +65,47 @@ const SelectionOverlay: React.FC<Props> = ({ overlay, dragRect, dragPreview = []
// selector might be invalid
}
}
setPersistentRects(rects);
rafRef.current = requestAnimationFrame(updateRects);
setPersistentRects((prev) => {
if (prev.length !== rects.length) return rects;
for (let i = 0; i < rects.length; i++) {
const a = prev[i], b = rects[i];
if (a.id !== b.id || a.top !== b.top || a.left !== b.left ||
a.width !== b.width || a.height !== b.height) return rects;
}
return prev;
});
};
rafRef.current = requestAnimationFrame(updateRects);
measureRects();
const scheduleUpdate = () => {
if (rafRef.current) cancelAnimationFrame(rafRef.current);
rafRef.current = requestAnimationFrame(measureRects);
};
window.addEventListener('scroll', scheduleUpdate, true);
window.addEventListener('resize', scheduleUpdate);
// Observe size changes on the selected elements specifically. We
// deliberately avoid a document-wide MutationObserver here — that would
// fire on every streamed chat token (and many other unrelated mutations)
// and effectively re-create the runaway loop we're replacing.
const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(scheduleUpdate) : null;
if (ro) {
for (const sel of semanticEls) {
try {
const domEl = document.querySelector(sel.selectorPath);
if (domEl) ro.observe(domEl);
} catch { /* selector might be invalid */ }
}
}
return () => {
if (rafRef.current) cancelAnimationFrame(rafRef.current);
window.removeEventListener('scroll', scheduleUpdate, true);
window.removeEventListener('resize', scheduleUpdate);
if (ro) ro.disconnect();
};
}, [ctx?.selectedElements]);
+22 -11
View File
@@ -109,6 +109,27 @@ function getAgentWorkTime(messages: Array<{ role: string; timestamp: string }>,
return { total, last };
}
const ElapsedTime: React.FC<{
messages: Array<{ role: string; timestamp: string }>;
status: string;
sx?: Record<string, any>;
}> = React.memo(({ messages, status, sx }) => {
const [, setTick] = useState(0);
const isLive = status === 'running' || status === 'waiting_approval';
useEffect(() => {
if (!isLive) return;
const interval = setInterval(() => setTick((t) => t + 1), 1000);
return () => clearInterval(interval);
}, [isLive]);
return (
<Typography variant="caption" sx={sx}>
{fmtSeconds(getAgentWorkTime(messages, status).last)}
</Typography>
);
});
function summarizeToolInput(toolName: string, toolInput: Record<string, any>): string {
const mcp = parseMcpToolName(toolName);
if (mcp.isMcp) {
@@ -295,7 +316,6 @@ const AgentCard: React.FC<Props> = ({
draft: { color: c.accent.primary, bg: c.bg.secondary },
};
const [, setTick] = useState(0);
const isDraft = session.status === 'draft';
// ---- Drag via header (pointer events) ----
@@ -485,13 +505,6 @@ const AgentCard: React.FC<Props> = ({
};
useEffect(() => {
if (session.status === 'running' || session.status === 'waiting_approval') {
const interval = setInterval(() => setTick((t) => t + 1), 1000);
return () => clearInterval(interval);
}
}, [session.status]);
const lastMessage = session.messages[session.messages.length - 1];
const isStreaming = !!session.streamingMessage;
const previewContent = isStreaming
@@ -881,9 +894,7 @@ const AgentCard: React.FC<Props> = ({
<Typography variant="caption" sx={{ color: c.text.tertiary }}>
{session.mode}
</Typography>
<Typography variant="caption" sx={{ color: c.text.tertiary }}>
{fmtSeconds(getAgentWorkTime(session.messages, session.status).last)}
</Typography>
<ElapsedTime messages={session.messages} status={session.status} sx={{ color: c.text.tertiary }} />
{session.cost_usd > 0 && hasApiKey && (
<Typography variant="caption" sx={{ color: c.accent.primary }}>
${session.cost_usd.toFixed(4)}
+42 -13
View File
@@ -115,6 +115,38 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
const glowingBrowserCards = useAppSelector((state) => state.dashboardLayout.glowingBrowserCards);
const sessionList = Object.values(sessions);
// Reference-stable list of browser-agent sessions that need tethers.
// Without this, the tether useMemo below recomputes ~190 lines of SVG
// path math on every streamed token in any chat, because `sessionList`
// is a fresh array on every Dashboard re-render.
const browserAgentTetherSeedsRef = useRef<
Array<{ id: string; browser_id: string; parent_session_id: string }>
>([]);
const browserAgentTetherSeeds = useMemo(() => {
const next: Array<{ id: string; browser_id: string; parent_session_id: string }> = [];
for (const s of Object.values(sessions)) {
if (s.mode !== 'browser-agent') continue;
if (s.status !== 'running' && s.status !== 'waiting_approval') continue;
if (!s.browser_id || !s.parent_session_id) continue;
next.push({ id: s.id, browser_id: s.browser_id, parent_session_id: s.parent_session_id });
}
const prev = browserAgentTetherSeedsRef.current;
if (prev.length === next.length) {
let same = true;
for (let i = 0; i < prev.length; i++) {
if (prev[i].id !== next[i].id
|| prev[i].browser_id !== next[i].browser_id
|| prev[i].parent_session_id !== next[i].parent_session_id) {
same = false;
break;
}
}
if (same) return prev;
}
browserAgentTetherSeedsRef.current = next;
return next;
}, [sessions]);
const contentBounds = useMemo(() => {
const allRects = [
...Object.values(cards).map((c) => ({ x: c.x, y: c.y, w: c.width, h: c.height })),
@@ -483,7 +515,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
useEffect(() => {
if (!pendingBrowserUrl || !layoutInitialized) return;
dispatch(addBrowserCard({ url: pendingBrowserUrl, expandedSessionIds }));
dispatch(addBrowserCard({ url: pendingBrowserUrl, expandedSessionIds, viewportWidth: window.innerWidth }));
dispatch(clearPendingBrowserUrl());
}, [pendingBrowserUrl, layoutInitialized, dispatch, expandedSessionIds]);
@@ -605,7 +637,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
const liveIds = dashboardSessionIds.sort().join(',');
if (liveIds === prevSessionIdsRef.current) return;
prevSessionIdsRef.current = liveIds;
dispatch(reconcileSessions({ sessionIds: dashboardSessionIds, expandedSessionIds }));
dispatch(reconcileSessions({ sessionIds: dashboardSessionIds, expandedSessionIds, viewportWidth: window.innerWidth }));
}, [sessions, layoutInitialized, dispatch, dashboardId, expandedSessionIds]);
// ---- Auto-reveal / collapse / unreveal sub-agent cards ----
@@ -1241,7 +1273,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
);
const handleAddView = useCallback((outputId: string) => {
dispatch(addViewCard({ outputId, expandedSessionIds }));
dispatch(addViewCard({ outputId, expandedSessionIds, viewportWidth: window.innerWidth }));
setTimeout(() => {
const card = store.getState().dashboardLayout.viewCards[outputId];
if (card) {
@@ -1254,7 +1286,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
const handleAddBrowser = useCallback(() => {
trackEvent('dashboard.browser_added');
const prevIds = new Set(Object.keys(store.getState().dashboardLayout.browserCards));
dispatch(addBrowserCard({ url: browserHomepage, expandedSessionIds }));
dispatch(addBrowserCard({ url: browserHomepage, expandedSessionIds, viewportWidth: window.innerWidth }));
setTimeout(() => {
const allBrowserCards = store.getState().dashboardLayout.browserCards;
const newId = Object.keys(allBrowserCards).find((id) => !prevIds.has(id));
@@ -1299,7 +1331,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
const handleTidy = useCallback(() => {
trackEvent('dashboard.tidy_layout');
const currentExpanded = store.getState().agents.expandedSessionIds;
dispatch(tidyLayout({ expandedSessionIds: currentExpanded }));
dispatch(tidyLayout({ expandedSessionIds: currentExpanded, viewportWidth: window.innerWidth }));
const expandedSet = new Set(currentExpanded);
const { cards: tidied, viewCards: tidiedViews, browserCards: tidiedBrowsers } = store.getState().dashboardLayout;
@@ -1563,20 +1595,17 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
}
// Source 2: active browser-agent sessions (persistent — survives parent turn completion)
for (const s of sessionList) {
if (s.mode !== 'browser-agent') continue;
if (s.status !== 'running' && s.status !== 'waiting_approval') continue;
if (!s.browser_id || !s.parent_session_id) continue;
if (glowTethers.has(s.browser_id)) continue; // glow already covers this one
const t = browserTether(s.browser_id, s.parent_session_id, false, '');
if (t) glowTethers.set(s.browser_id, t);
for (const seed of browserAgentTetherSeeds) {
if (glowTethers.has(seed.browser_id)) continue; // glow already covers this one
const t = browserTether(seed.browser_id, seed.parent_session_id, false, '');
if (t) glowTethers.set(seed.browser_id, t);
}
const browserTethers = Array.from(glowTethers.values()).filter(Boolean) as Array<{ key: string; path: string; labelX: number; labelY: number; label: string; fading: boolean }>;
return [...agentTethers, ...browserTethers];
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [glowingAgentCards, glowingBrowserCards, cards, browserCards, expandedSessionIds, liveDragInfo, measuredHeightsTick, sessionList]);
}, [glowingAgentCards, glowingBrowserCards, cards, browserCards, expandedSessionIds, liveDragInfo, measuredHeightsTick, browserAgentTetherSeeds]);
const dotSize = Math.max(1, 1.5 * canvas.zoom);
const dotSpacing = 24 * canvas.zoom;
@@ -185,12 +185,14 @@ export function findOpenGridCell(
occupiedRects: Rect[],
newW: number,
newH: number,
viewportWidth?: number,
): { x: number; y: number } {
const cellW = DEFAULT_CARD_W + GRID_GAP;
const cellH = DEFAULT_CARD_H + GRID_GAP;
const vw = viewportWidth ?? (typeof window !== 'undefined' ? window.innerWidth : 0);
const maxCols = Math.max(
1,
Math.floor((window.innerWidth - GRID_ORIGIN.x) / cellW) || GRID_COLS_FALLBACK,
Math.floor((vw - GRID_ORIGIN.x) / cellW) || GRID_COLS_FALLBACK,
);
for (let row = 0; ; row++) {
@@ -265,9 +267,9 @@ const dashboardLayoutSlice = createSlice({
reconcileSessions(
state,
action: PayloadAction<{ sessionIds: string[]; expandedSessionIds: string[] }>,
action: PayloadAction<{ sessionIds: string[]; expandedSessionIds: string[]; viewportWidth?: number }>,
) {
const { sessionIds, expandedSessionIds } = action.payload;
const { sessionIds, expandedSessionIds, viewportWidth } = action.payload;
const liveIds = new Set(sessionIds);
for (const id of Object.keys(state.cards)) {
@@ -287,7 +289,7 @@ const dashboardLayoutSlice = createSlice({
delete state.closedCardPositions[id];
} else {
const rects = collectOccupiedRects(state, expandedSessionIds);
const pos = findOpenGridCell(rects, DEFAULT_CARD_W, DEFAULT_CARD_H);
const pos = findOpenGridCell(rects, DEFAULT_CARD_W, DEFAULT_CARD_H, viewportWidth);
state.cards[id] = {
session_id: id,
x: pos.x,
@@ -302,8 +304,9 @@ const dashboardLayoutSlice = createSlice({
tidyLayout(
state,
action: PayloadAction<{ expandedSessionIds: string[] }>,
action: PayloadAction<{ expandedSessionIds: string[]; viewportWidth?: number }>,
) {
const { viewportWidth } = action.payload;
const expanded = new Set(action.payload.expandedSessionIds);
const agentCards = Object.values(state.cards);
const viewCards = Object.values(state.viewCards);
@@ -330,7 +333,7 @@ const dashboardLayoutSlice = createSlice({
h = item.storedH;
}
const pos = findOpenGridCell(placedRects, w, h);
const pos = findOpenGridCell(placedRects, w, h, viewportWidth);
placedRects.push({ x: pos.x, y: pos.y, w, h });
if (item.kind === 'agent') {
@@ -349,8 +352,9 @@ const dashboardLayoutSlice = createSlice({
addViewCard(state, action: PayloadAction<{
outputId: string; expandedSessionIds?: string[];
x?: number; y?: number; width?: number; height?: number;
viewportWidth?: number;
}>) {
const { outputId, expandedSessionIds, x, y, width, height } = action.payload;
const { outputId, expandedSessionIds, x, y, width, height, viewportWidth } = action.payload;
if (state.viewCards[outputId]) return;
let posX: number, posY: number;
if (x != null && y != null) {
@@ -358,7 +362,7 @@ const dashboardLayoutSlice = createSlice({
posY = y;
} else {
const rects = collectOccupiedRects(state, expandedSessionIds);
const pos = findOpenGridCell(rects, DEFAULT_VIEW_CARD_W, DEFAULT_VIEW_CARD_H);
const pos = findOpenGridCell(rects, DEFAULT_VIEW_CARD_W, DEFAULT_VIEW_CARD_H, viewportWidth);
posX = pos.x;
posY = pos.y;
}
@@ -397,11 +401,11 @@ const dashboardLayoutSlice = createSlice({
delete state.viewCards[action.payload];
},
addBrowserCard(state, action: PayloadAction<{ url: string; expandedSessionIds?: string[] }>) {
addBrowserCard(state, action: PayloadAction<{ url: string; expandedSessionIds?: string[]; viewportWidth?: number }>) {
const id = `browser-${Date.now().toString(36)}`;
const tabId = generateTabId();
const rects = collectOccupiedRects(state, action.payload.expandedSessionIds);
const pos = findOpenGridCell(rects, DEFAULT_BROWSER_CARD_W, DEFAULT_BROWSER_CARD_H);
const pos = findOpenGridCell(rects, DEFAULT_BROWSER_CARD_W, DEFAULT_BROWSER_CARD_H, action.payload.viewportWidth);
state.browserCards[id] = {
browser_id: id,
url: action.payload.url,
@@ -462,9 +466,10 @@ const dashboardLayoutSlice = createSlice({
action: PayloadAction<{
tabs: BrowserTab[]; url: string; expandedSessionIds?: string[];
id?: string; x?: number; y?: number; width?: number; height?: number;
viewportWidth?: number;
}>
) {
const { x, y, width, height } = action.payload;
const { x, y, width, height, viewportWidth } = action.payload;
const id = action.payload.id || `browser-${Date.now().toString(36)}`;
const newTabs = action.payload.tabs.map((t) => ({
id: generateTabId(),
@@ -479,7 +484,7 @@ const dashboardLayoutSlice = createSlice({
posY = y;
} else {
const rects = collectOccupiedRects(state, action.payload.expandedSessionIds);
const pos = findOpenGridCell(rects, DEFAULT_BROWSER_CARD_W, DEFAULT_BROWSER_CARD_H);
const pos = findOpenGridCell(rects, DEFAULT_BROWSER_CARD_W, DEFAULT_BROWSER_CARD_H, viewportWidth);
posX = pos.x;
posY = pos.y;
}