[hAIk]: refactor AgentCard and BrowserCard into subdirectory modules, plumb dashboard_id from launch through Agent/AgentSnapshot, add on_done auto-persist callback, fix get_all_sessions to use GET query params, atomic PydanticStore writes, graceful backend shutdown in local.sh, and add mode config JSONs

This commit is contained in:
haikdc
2026-04-18 12:05:50 -07:00
parent 21a723a45b
commit 6a9dd59eec
22 changed files with 139 additions and 91 deletions
@@ -13,11 +13,11 @@ import { setCardPosition, setCardSize, fadeGlowingAgentCard, clearGlowingAgentCa
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
import AgentChat from '@/app/pages/AgentChat/AgentChat';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { useOverlayScrollPassthrough } from './useOverlayScrollPassthrough';
import { type ResizeDir, DRAG_THRESHOLD, CURSOR_MAP, HANDLE_DEFS } from './cardLayoutConstants';
import CardGlowOverlay from './CardGlowOverlay';
import AgentCardCollapsed from './AgentCardCollapsed';
import { formatDuration, getStatusColors, getPreviewContent } from './agentCardUtils';
import { useOverlayScrollPassthrough } from '@/app/pages/Dashboard/useOverlayScrollPassthrough';
import { type ResizeDir, DRAG_THRESHOLD, CURSOR_MAP, HANDLE_DEFS } from '@/app/pages/Dashboard/cardLayoutConstants';
import CardGlowOverlay from './components/CardGlowOverlay';
import AgentCardCollapsed from './components/AgentCardCollapsed';
import { formatDuration, getStatusColors, getPreviewContent } from './components/agentCardUtils';
interface Props {
session: AgentSession; expanded: boolean;
@@ -6,14 +6,15 @@ import { useAppDispatch, useAppSelector } from '@/shared/hooks';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { useBrowserActivity } from '@/shared/useBrowserActivity';
import { resolveInput, isGoogleSearch } from '@/shared/resolveUrl';
import BrowserAgentOverlay from './BrowserAgentOverlay';
import { useOverlayScrollPassthrough } from './useOverlayScrollPassthrough';
import BrowserAgentOverlay from './components/BrowserAgentOverlay/BrowserAgentOverlay';
import { useOverlayScrollPassthrough } from '@/app/pages/Dashboard/useOverlayScrollPassthrough';
import { useElementSelection } from '@/app/components/ElementSelectionContext';
import { type ResizeDir, CURSOR_MAP, HANDLE_DEFS, DRAG_THRESHOLD } from './cardLayoutConstants';
import { useWebviewLifecycle, isElectron, chromeUserAgent, webviewPreloadPath, type TabLocalState, type WebviewElement } from './hooks/useWebviewLifecycle';
import BrowserTabBar from './BrowserTabBar';
import BrowserNavBar from './BrowserNavBar';
import BrowserActionOverlay from './BrowserActionOverlay';
import { type ResizeDir, CURSOR_MAP, HANDLE_DEFS, DRAG_THRESHOLD } from '@/app/pages/Dashboard/cardLayoutConstants';
import { useWebviewLifecycle, isElectron, chromeUserAgent, webviewPreloadPath, type WebviewElement } from './hooks/useWebviewLifecycle';
import type { TabLocalState } from '@/app/pages/Dashboard/types/types';
import BrowserTabBar from './components/BrowserTabBar';
import BrowserNavBar from './components/BrowserNavBar';
import BrowserActionOverlay from './components/BrowserActionOverlay';
const MIN_W = 400, MIN_H = 300;
@@ -9,14 +9,53 @@ import OpenInFullIcon from '@mui/icons-material/OpenInFull';
import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen';
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import { AgentSession } from '@/shared/state/agentsSlice';
import { AgentMessage, AgentSession } from '@/shared/state/agentsSlice';
import { STOP_AGENT } from '@/shared/backend-bridge/apps/agents';
import { useAppDispatch } from '@/shared/hooks';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { summarizeMessage } from './browserAgentOverlayUtils';
import { OverlayEntry } from './OverlayEntry';
import OverlayActionLog from './OverlayActionLog';
export function summarizeMessage(msg: AgentMessage): OverlayEntry {
if (msg.role === 'assistant' && typeof msg.content === 'string') {
const trimmed = msg.content.trim();
if (!trimmed) return { type: 'skip', text: '' };
return { type: 'thought', text: trimmed };
}
if (msg.role === 'tool_call') {
const content = typeof msg.content === 'string'
? (() => { try { return JSON.parse(msg.content); } catch { return {}; } })()
: msg.content;
const tool = content?.tool || content?.name || '?';
const input = content?.input || {};
let brief = '';
switch (tool) {
case 'BrowserNavigate': brief = `Navigate → ${input.url || '...'}`; break;
case 'BrowserClick': brief = `Click ${input.selector || '...'}`; break;
case 'BrowserType':
brief = `Type "${(input.text || '').slice(0, 30)}${(input.text || '').length > 30 ? '…' : ''}" into ${input.selector || '...'}`;
break;
case 'BrowserScreenshot': brief = 'Screenshot'; break;
case 'BrowserGetText': brief = 'Read page text'; break;
case 'BrowserGetElements':
brief = `Inspect elements${input.selector ? ` (${input.selector})` : ''}`;
break;
case 'BrowserEvaluate': brief = 'Evaluate JS'; break;
default: brief = tool;
}
return { type: 'action', text: brief };
}
if (msg.role === 'tool_result') {
return { type: 'result', text: '' };
}
return { type: 'skip', text: '' };
}
interface Props {
session: AgentSession;
browserWidth: number;
@@ -2,7 +2,7 @@ import React, { useRef, useEffect } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { OverlayEntry } from './browserAgentOverlayUtils';
import { OverlayEntry } from './OverlayEntry';
interface Props {
entries: OverlayEntry[];
@@ -0,0 +1,4 @@
export interface OverlayEntry {
type: 'thought' | 'action' | 'result' | 'skip';
text: string;
}
@@ -13,7 +13,7 @@ import {
reorderBrowserTab, setActiveBrowserTab, addBrowserTab,
removeBrowserTab, removeBrowserCard, type BrowserTab,
} from '@/shared/state/dashboardLayoutSlice';
import type { TabLocalState } from './hooks/useWebviewLifecycle';
import type { TabLocalState } from '@/app/pages/Dashboard/types/types';
interface BrowserTabBarProps {
tabs: BrowserTab[];
@@ -1,8 +1,8 @@
import { useRef, useEffect } from 'react';
import { useAppDispatch } from '@/shared/hooks';
import {
registerWebview,
unregisterWebview,
registerWebview, // only used in this file, maybe an aNr opportunity? -HD
unregisterWebview, // only used in this file, maybe an aNr opportunity? -HD
setActiveTab as setRegistryActiveTab,
type BrowserWebview,
} from '@/shared/browserRegistry';
@@ -12,15 +12,10 @@ import {
updateBrowserTabFavicon,
type BrowserTab,
} from '@/shared/state/dashboardLayoutSlice';
import type { TabLocalState } from '@/app/pages/Dashboard/types/types';
export type WebviewElement = BrowserWebview;
export interface TabLocalState {
loading: boolean;
canGoBack: boolean;
canGoForward: boolean;
}
export const isElectron = navigator.userAgent.includes('Electron');
export const chromeUserAgent = navigator.userAgent
@@ -2,9 +2,9 @@ import React from 'react';
import { AnimatePresence } from 'framer-motion';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import AgentCard from './AgentCard';
import AgentCard from './AgentCard/AgentCard';
import DashboardViewCard from './DashboardViewCard';
import BrowserCard from './BrowserCard';
import BrowserCard from './BrowserCard/BrowserCard';
import CanvasControls from './CanvasControls';
import DashboardToolbar from './DashboardToolbar';
import DashboardHeader from './DashboardHeader';
@@ -1,44 +0,0 @@
import { AgentMessage } from '@/shared/state/agentsSlice';
export interface OverlayEntry {
type: 'thought' | 'action' | 'result' | 'skip';
text: string;
}
export function summarizeMessage(msg: AgentMessage): OverlayEntry {
if (msg.role === 'assistant' && typeof msg.content === 'string') {
const trimmed = msg.content.trim();
if (!trimmed) return { type: 'skip', text: '' };
return { type: 'thought', text: trimmed };
}
if (msg.role === 'tool_call') {
const content = typeof msg.content === 'string'
? (() => { try { return JSON.parse(msg.content); } catch { return {}; } })()
: msg.content;
const tool = content?.tool || content?.name || '?';
const input = content?.input || {};
let brief = '';
switch (tool) {
case 'BrowserNavigate': brief = `Navigate → ${input.url || '...'}`; break;
case 'BrowserClick': brief = `Click ${input.selector || '...'}`; break;
case 'BrowserType':
brief = `Type "${(input.text || '').slice(0, 30)}${(input.text || '').length > 30 ? '…' : ''}" into ${input.selector || '...'}`;
break;
case 'BrowserScreenshot': brief = 'Screenshot'; break;
case 'BrowserGetText': brief = 'Read page text'; break;
case 'BrowserGetElements':
brief = `Inspect elements${input.selector ? ` (${input.selector})` : ''}`;
break;
case 'BrowserEvaluate': brief = 'Evaluate JS'; break;
default: brief = tool;
}
return { type: 'action', text: brief };
}
if (msg.role === 'tool_result') {
return { type: 'result', text: '' };
}
return { type: 'skip', text: '' };
}
@@ -101,7 +101,7 @@ export function useDashboardInit(deps: InitDeps) {
if (!layoutInitialized) return;
const dashboardSessionIds = Object.values(sessions)
.filter((s: any) => s.dashboard_id === dashboardId && s.mode !== 'browser-agent' && s.mode !== 'invoked-agent' && s.mode !== 'sub-agent')
.map((s: any) => s.id);
.map((s: any) => s.session_id);
const liveIds = dashboardSessionIds.sort().join(',');
if (liveIds === prevSessionIdsRef.current) return;
prevSessionIdsRef.current = liveIds;
@@ -0,0 +1,6 @@
export interface TabLocalState {
loading: boolean;
canGoBack: boolean;
canGoForward: boolean;
}
@@ -15,13 +15,12 @@ export const AGENTS_WS_API: string = `${API_BASE}/agents/ws`;
const get_all_sessions_endpoint: string = `${AGENTS_API}/get_all_sessions`;
async function get_all_sessions_function(dashboardId?: string): Promise<AgentSession[]> {
const res = await fetch(get_all_sessions_endpoint, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ dashboard_id: dashboardId ?? '' }),
});
const url = dashboardId
? `${get_all_sessions_endpoint}?dashboard_id=${encodeURIComponent(dashboardId)}`
: get_all_sessions_endpoint;
const res = await fetch(url);
const data = await res.json();
return data.SESSIONS as AgentSession[];
return data.sessions as AgentSession[];
}
export const GET_ALL_SESSIONS = createAsyncThunk(
get_all_sessions_endpoint,
@@ -53,6 +52,7 @@ async function launch_agent_function(config: {
mode: string;
system_prompt: string;
max_turns: number;
dashboard_id?: string;
}): Promise<{ session_id: string; session: AgentSession }> {
const res = await fetch(launch_agent_endpoint, {
method: 'POST',
@@ -62,6 +62,7 @@ async function launch_agent_function(config: {
mode: config.mode,
system_prompt: config.system_prompt,
max_turns: config.max_turns,
dashboard_id: config.dashboard_id,
}),
});
const data = await res.json();
@@ -349,6 +350,7 @@ async function meta_launch_and_send_function(
mode: payload.mode,
system_prompt: payload.config.system_prompt ?? '',
max_turns: payload.config.max_turns ?? 100,
dashboard_id: payload.config.dashboard_id,
});
console.log(`[FRONTEND] meta_launch_and_send: launched | draftId=${payload.draftId} → realId=${session.session_id} status=${session.status} dashboard_id=${session.dashboard_id ?? 'NONE'}`);