mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-02 14:28:59 +02:00
[eric] chat: the live browser anchors at its browser tool row, not the transcript tail, and never pops to canvas when its row windows out
This commit is contained in:
@@ -1248,6 +1248,19 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
|
||||
return items;
|
||||
}, [activeBranchMessages, sessionRunning]);
|
||||
|
||||
// The docked browser anchors at the LAST browser-agent tool row (one live browser, latest work site wins); no row yet falls back to the end-of-transcript slot.
|
||||
const browserAnchorItemId = useMemo((): string | null => {
|
||||
const isBrowserCall = (m: AgentMessage): boolean =>
|
||||
m.role === 'tool_call' && typeof m.content === 'object' && String((m.content as { tool?: string })?.tool || '').toLowerCase().endsWith('browseragent');
|
||||
let anchor: string | null = null;
|
||||
for (const item of renderItems) {
|
||||
if (isToolGroup(item)) { if (item.pairs.some((p) => isBrowserCall(p.call))) anchor = item.id; }
|
||||
else if (isToolPair(item)) { if (isBrowserCall(item.call)) anchor = item.id; }
|
||||
else if (isBrowserCall(item as AgentMessage)) anchor = item.id;
|
||||
}
|
||||
return anchor;
|
||||
}, [renderItems]);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
const total = renderItems.length;
|
||||
renderItemsLengthRef.current = total;
|
||||
@@ -1701,6 +1714,7 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
|
||||
<Box aria-hidden data-window-spacer="top" sx={{ height: topSpacerHeight, flexShrink: 0, overflowAnchor: 'none' }} />
|
||||
)}
|
||||
{renderedVisibleItems.map((item, itemIdx) => {
|
||||
const rendered = ((): React.ReactNode => {
|
||||
const isLastVisibleItem = itemIdx === renderedVisibleItems.length - 1;
|
||||
const isCompactionAnchor = !!session.compacted_through_msg_id && item.id === session.compacted_through_msg_id;
|
||||
const compactionChip = isCompactionAnchor ? (
|
||||
@@ -1841,6 +1855,17 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})();
|
||||
// The live browser anchors AT the browser tool row (ChatGPT-agent model): the view sits where the work happened, above the answer that follows it.
|
||||
if (hasDockedBrowser && browserAnchorItemId && item.id === browserAnchorItemId) {
|
||||
return (
|
||||
<React.Fragment key={`${item.id}-with-browser`}>
|
||||
{rendered}
|
||||
<Box data-browser-slot={id} sx={{ height: 'min(360px, 38vh)', minHeight: 180, mt: 1, mb: 0.5 }} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
return rendered;
|
||||
})}
|
||||
{/* Stand-in for items unmounted BELOW the window (newer items not yet
|
||||
scrolled into view). Zero while following the live tail. */}
|
||||
@@ -1987,8 +2012,8 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
{/* Inline dock slot: the agent's browser rides HERE, in the transcript flow like a tool output (the real card overlays this rect geometrically, so the webview never remounts). It scrolls with the conversation; the mini hides itself when this scrolls mostly out of view, since a live webview can't be clipped by the scroller. */}
|
||||
{hasDockedBrowser && (
|
||||
{/* Fallback dock slot for a browser that docked before any browser tool row exists (or whose row was compacted away); once a row appears the slot anchors at it instead (see browserAnchorItemId). The real card overlays this rect geometrically, so the webview never remounts; the mini hides itself when its slot scrolls mostly out of view, since a live webview can't be clipped by the scroller. */}
|
||||
{hasDockedBrowser && !browserAnchorItemId && (
|
||||
<Box
|
||||
data-browser-slot={id}
|
||||
sx={{ height: 'min(360px, 38vh)', minHeight: 180, mt: 1, mb: 0.5 }}
|
||||
|
||||
@@ -260,7 +260,8 @@ const BrowserCard: React.FC<Props> = ({
|
||||
const measure = (): void => {
|
||||
const slot = document.querySelector(`[data-browser-slot="${dockedTo}"]`);
|
||||
const layer = rootElRef.current?.parentElement;
|
||||
if (!slot || !layer) { setDockRect(null); return; }
|
||||
// The slot lives in the transcript's virtualized window, so it can UNMOUNT while scrolled far away; keep the last rect and hide, because nulling it here popped the card back to its stale canvas home.
|
||||
if (!slot || !layer) { setDockVisible(false); return; }
|
||||
// The slot mounts a beat after docking (and remounts with chat re-renders), so observers hook the live node whenever it changes; a one-shot hookup at effect time reliably missed it and froze the rect.
|
||||
if (slot !== hookedSlot) {
|
||||
ro.disconnect();
|
||||
@@ -1030,6 +1031,8 @@ const BrowserCard: React.FC<Props> = ({
|
||||
: c.shadow.md;
|
||||
|
||||
const dockActive = !!dockRect && !dragging && !localResize && !isTiled && !keepAliveHidden && !isMinimized;
|
||||
// Docked in intent but no slot rect yet (slot not mounted, or windowed out before first measure): hide rather than flash the card at its stale canvas home.
|
||||
const dockPending = !!dockedTo && !!dockParentCard && dockParentExpanded && !dockRect && !dragging && !isTiled && !isMinimized && !keepAliveHidden;
|
||||
// An agent can only SEE a page the compositor is drawing, and Chromium draws nothing at all for a
|
||||
// guest parked at left:-100000. Measured in one window: a card on screen captured in 58ms while
|
||||
// the same card parked timed out on guest capturePage, on host capturePage AND on CDP
|
||||
@@ -1114,7 +1117,7 @@ const BrowserCard: React.FC<Props> = ({
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
// Kept-alive card from another dashboard: parked far off-screen so its webview surface can't bleed onto the dashboard you're viewing; click-through, webContents stays mounted. A dock-hidden mini (slot scrolled away) is click-through too.
|
||||
pointerEvents: keepAliveHidden || isMinimized || dockParked || (dockActive && !dockVisible) ? 'none' : undefined,
|
||||
pointerEvents: keepAliveHidden || isMinimized || dockParked || dockPending || (dockActive && !dockVisible) ? 'none' : undefined,
|
||||
// contain: webview repaints don't shake neighbor cards.
|
||||
contain: 'layout style',
|
||||
// Own compositor layer so hover/paint invalidations stay contained to this card. See AgentCard for full rationale.
|
||||
@@ -1137,7 +1140,7 @@ const BrowserCard: React.FC<Props> = ({
|
||||
flexDirection: 'column',
|
||||
zIndex: isTiled ? 999990 : (isDragging || isResizing) ? 999999 : dockActive ? (dockParentTiled ? 999991 : dockParentZ + 1) : cardZOrder,
|
||||
// The inline slot scrolls with the transcript; a webview can't be clipped by the scroller, so the mini fades out when its slot is mostly out of view instead of floating over unrelated messages.
|
||||
opacity: dockActive && !dockVisible ? 0 : 1,
|
||||
opacity: (dockActive && !dockVisible) || dockPending ? 0 : 1,
|
||||
transition: noTransition ? 'none' : 'box-shadow 0.4s ease, border 0.3s ease, opacity 0.14s ease',
|
||||
'&:hover .resize-handle': { opacity: 1 },
|
||||
...(isHighlighted && {
|
||||
|
||||
Reference in New Issue
Block a user