[Haik]: round 1 of browser use animations (glowing links) (auto spawn in position set)

This commit is contained in:
haikdc
2026-03-20 22:44:50 -07:00
parent c8b2726ec7
commit 99695253ea
5 changed files with 82 additions and 15 deletions
@@ -42,7 +42,7 @@ import ApprovalBar, { BatchApprovalBar } from './ApprovalBar';
import ChatInput, { ChatInputHandle } from './ChatInput';
import { ContextPath } from '@/app/components/DirectoryBrowser';
import DiffViewer from './DiffViewer';
import { setGlowingBrowserCards, clearGlowingBrowserCards } from '@/shared/state/dashboardLayoutSlice';
import { setGlowingBrowserCards, fadeGlowingBrowserCards, clearGlowingBrowserCards } from '@/shared/state/dashboardLayoutSlice';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
const CONTEXT_WINDOWS: Record<string, number> = {
@@ -204,13 +204,13 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
const realId = action.payload.session.id;
dispatch(generateTitle({ sessionId: realId, prompt: msg.prompt }));
if (msg.selectedBrowserIds?.length) {
dispatch(setGlowingBrowserCards({ browserIds: msg.selectedBrowserIds, sessionId: realId }));
dispatch(setGlowingBrowserCards({ browserIds: msg.selectedBrowserIds, sessionId: realId, label: 'Use Browser' }));
}
}
});
} else {
if (msg.selectedBrowserIds?.length) {
dispatch(setGlowingBrowserCards({ browserIds: msg.selectedBrowserIds, sessionId: id }));
dispatch(setGlowingBrowserCards({ browserIds: msg.selectedBrowserIds, sessionId: id, label: 'Use Browser' }));
}
dispatch(sendMessageThunk({ sessionId: id, prompt: msg.prompt, mode, model, images: msg.images, contextPaths: msg.contextPaths, forcedTools: msg.forcedTools, attachedSkills: msg.attachedSkills, selectedBrowserIds: msg.selectedBrowserIds }))
.then((action) => {
@@ -234,7 +234,10 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
const isTerminal = curr === 'completed' || curr === 'stopped' || curr === 'error';
if (wasActive && isTerminal) {
if (id) dispatch(clearGlowingBrowserCards(id));
if (id) {
dispatch(fadeGlowingBrowserCards(id));
setTimeout(() => dispatch(clearGlowingBrowserCards(id)), 2800);
}
const nextQueued = messageQueueRef.current.shift();
if (nextQueued) {
+54 -3
View File
@@ -34,6 +34,7 @@ import {
removeBrowserCard,
pasteBrowserCard,
placeCard,
setCardPosition,
removeCard,
bringToFront,
setGlowingAgentCard,
@@ -103,6 +104,7 @@ const DashboardInner: React.FC = () => {
const autoRevealSubAgents = useAppSelector((state) => state.settings.data.auto_reveal_sub_agents);
const outputs = useAppSelector((state) => state.outputs.items);
const glowingAgentCards = useAppSelector((state) => state.dashboardLayout.glowingAgentCards);
const glowingBrowserCards = useAppSelector((state) => state.dashboardLayout.glowingBrowserCards);
const sessionList = Object.values(sessions);
const canvas = useCanvasControls(zoomSensitivity);
@@ -752,7 +754,18 @@ const DashboardInner: React.FC = () => {
const realId = action.payload.session.id;
dispatch(generateTitle({ sessionId: realId, prompt }));
if (selectedBrowserIds?.length) {
dispatch(setGlowingBrowserCards({ browserIds: selectedBrowserIds, sessionId: realId }));
dispatch(setGlowingBrowserCards({ browserIds: selectedBrowserIds, sessionId: realId, label: 'Use Browser' }));
if (selectedBrowserIds.length === 1) {
const bc = store.getState().dashboardLayout.browserCards[selectedBrowserIds[0]];
if (bc) {
dispatch(setCardPosition({
sessionId: realId,
x: bc.x - DEFAULT_CARD_W - GRID_GAP * 12,
y: bc.y,
}));
}
}
}
spawnOriginsRef.current[realId] = spawnOriginsRef.current[draftId];
delete spawnOriginsRef.current[draftId];
@@ -919,7 +932,7 @@ const DashboardInner: React.FC = () => {
].join(' ');
}
return Object.entries(glowingAgentCards).map(([copyId, { sourceId, fading, sourceYRatio, label }]) => {
const agentTethers = Object.entries(glowingAgentCards).map(([copyId, { sourceId, fading, sourceYRatio, label }]) => {
const src = cards[sourceId];
const dst = cards[copyId];
if (!src || !dst) return null;
@@ -957,8 +970,46 @@ const DashboardInner: React.FC = () => {
fading,
};
}).filter(Boolean) as Array<{ key: string; path: string; labelX: number; labelY: number; label: string; fading: boolean }>;
const browserTethers = Object.entries(glowingBrowserCards).map(([browserId, { sourceId, fading, label }]) => {
const src = cards[sourceId];
const dst = browserCards[browserId];
if (!src || !dst) return null;
let srcX = src.x, srcY = src.y;
let dstX = dst.x, dstY = dst.y;
if (liveDragInfo) {
if (liveDragInfo.cardId === sourceId) { srcX += liveDragInfo.dx; srcY += liveDragInfo.dy; }
if (liveDragInfo.cardId === browserId) { dstX += liveDragInfo.dx; dstY += liveDragInfo.dy; }
}
const srcMeasured = measuredHeightsRef.current[sourceId];
const srcH = srcMeasured ?? (expandedSessionIds.includes(sourceId)
? Math.max(EXPANDED_CARD_MIN_H, src.height)
: src.height);
const dstH = dst.height;
const x1 = srcX + src.width;
const y1 = srcY + srcH * 0.54;
const x2 = dstX;
const y2 = dstY + dstH * 0.54;
const midX = x1 + (x2 - x1) / 2;
const labelX = midX + (x2 - midX) * 0.15;
const labelY = y2;
return {
key: `browser-${browserId}`,
path: elbowPath(x1, y1, x2, y2),
labelX,
labelY,
label: label || '',
fading,
};
}).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, cards, expandedSessionIds, liveDragInfo, measuredHeightsTick]);
}, [glowingAgentCards, glowingBrowserCards, cards, browserCards, expandedSessionIds, liveDragInfo, measuredHeightsTick]);
const dotSize = Math.max(1, 1.5 * canvas.zoom);
const dotSpacing = 24 * canvas.zoom;
@@ -57,7 +57,7 @@ export interface DashboardLayoutState {
viewCards: Record<string, ViewCardPosition>;
browserCards: Record<string, BrowserCardPosition>;
closedCardPositions: Record<string, CardPosition>;
glowingBrowserCards: Record<string, string>;
glowingBrowserCards: Record<string, { sourceId: string; fading: boolean; label?: string }>;
glowingAgentCards: Record<string, { sourceId: string; fading: boolean; sourceYRatio?: number; label?: string }>;
persistedExpandedSessionIds: string[];
nextZOrder: number;
@@ -632,18 +632,25 @@ const dashboardLayoutSlice = createSlice({
setGlowingBrowserCards(
state,
action: PayloadAction<{ browserIds: string[]; sessionId: string }>
action: PayloadAction<{ browserIds: string[]; sessionId: string; label?: string }>
) {
const { browserIds, sessionId } = action.payload;
const { browserIds, sessionId, label } = action.payload;
for (const id of browserIds) {
state.glowingBrowserCards[id] = sessionId;
state.glowingBrowserCards[id] = { sourceId: sessionId, fading: false, label };
}
},
fadeGlowingBrowserCards(state, action: PayloadAction<string>) {
const sessionId = action.payload;
for (const entry of Object.values(state.glowingBrowserCards)) {
if (entry.sourceId === sessionId) entry.fading = true;
}
},
clearGlowingBrowserCards(state, action: PayloadAction<string>) {
const sessionId = action.payload;
for (const [browserId, sid] of Object.entries(state.glowingBrowserCards)) {
if (sid === sessionId) delete state.glowingBrowserCards[browserId];
for (const [browserId, entry] of Object.entries(state.glowingBrowserCards)) {
if (entry.sourceId === sessionId) delete state.glowingBrowserCards[browserId];
}
},
@@ -751,6 +758,7 @@ export const {
reorderBrowserTab,
moveCards,
setGlowingBrowserCards,
fadeGlowingBrowserCards,
clearGlowingBrowserCards,
clearAllGlowingBrowserCards,
setGlowingAgentCard,
+6 -1
View File
@@ -16,7 +16,7 @@ import {
closeSessionFromWs,
trackAgentNotification,
} from '../state/agentsSlice';
import { addBrowserCardFromBackend, setBrowserCardPosition, GRID_GAP } from '../state/dashboardLayoutSlice';
import { addBrowserCardFromBackend, setBrowserCardPosition, setGlowingBrowserCards, GRID_GAP } from '../state/dashboardLayoutSlice';
type WSEvent = {
event: string;
@@ -249,6 +249,11 @@ class WebSocketManager {
x: parentCard.x + parentCard.width + GRID_GAP * 12,
y: parentCard.y,
}));
store.dispatch(setGlowingBrowserCards({
browserIds: [data.browser_card.browser_id],
sessionId: parentId,
label: 'Use Browser',
}));
}
}
}
File diff suppressed because one or more lines are too long