mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-11 20:27:44 +02:00
[aidan] chore/dashboard: drop dead configure/missed-run cards, refetch on reconnect
This commit is contained in:
@@ -58,7 +58,6 @@ class DashboardLayout(BaseModel):
|
||||
view_cards: dict[str, ViewCardPosition] = Field(default_factory=dict)
|
||||
browser_cards: dict[str, BrowserCardPosition] = Field(default_factory=dict)
|
||||
workflow_cards: dict = Field(default_factory=dict)
|
||||
configure_panels: dict = Field(default_factory=dict)
|
||||
workflows_hub: Optional[dict] = None
|
||||
notes: dict[str, NotePosition] = Field(default_factory=dict)
|
||||
expanded_session_ids: list[str] = Field(default_factory=list)
|
||||
|
||||
@@ -14,7 +14,6 @@ import type {
|
||||
NotePosition,
|
||||
WorkflowCardPosition,
|
||||
WorkflowsHubPosition,
|
||||
ConfigurePanelPosition,
|
||||
} from '@/shared/state/dashboardLayoutSlice';
|
||||
import type { Output } from '@/shared/state/outputsSlice';
|
||||
import type { CardType, useDashboardSelection } from '../hooks/state/useDashboardSelection';
|
||||
@@ -43,7 +42,6 @@ interface DashboardCanvasProps {
|
||||
notes: Record<string, NotePosition>;
|
||||
workflowCards: Record<string, WorkflowCardPosition>;
|
||||
workflowsHub: WorkflowsHubPosition | null;
|
||||
configurePanels: Record<string, ConfigurePanelPosition>;
|
||||
outputs: Record<string, Output>;
|
||||
glowingAgentCards: Record<string, GlowingAgentCard>;
|
||||
expandedSessionIds: string[];
|
||||
@@ -106,7 +104,6 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
|
||||
notes,
|
||||
workflowCards,
|
||||
workflowsHub,
|
||||
configurePanels,
|
||||
outputs,
|
||||
glowingAgentCards,
|
||||
expandedSessionIds,
|
||||
@@ -251,7 +248,6 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
|
||||
notes={notes}
|
||||
workflowCards={workflowCards}
|
||||
workflowsHub={workflowsHub}
|
||||
configurePanels={configurePanels}
|
||||
outputs={outputs}
|
||||
glowingAgentCards={glowingAgentCards}
|
||||
expandedSessionIds={expandedSessionIds}
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
type NotePosition,
|
||||
type WorkflowCardPosition,
|
||||
type WorkflowsHubPosition,
|
||||
type ConfigurePanelPosition,
|
||||
} from '@/shared/state/dashboardLayoutSlice';
|
||||
import { useAppSelector, useAppDispatch } from '@/shared/hooks';
|
||||
import { closeWorkflowMonitor } from '@/shared/state/dashboardLayoutSlice';
|
||||
@@ -36,7 +35,6 @@ interface DashboardCardLayerProps {
|
||||
notes: Record<string, NotePosition>;
|
||||
workflowCards: Record<string, WorkflowCardPosition>;
|
||||
workflowsHub: WorkflowsHubPosition | null;
|
||||
configurePanels: Record<string, ConfigurePanelPosition>;
|
||||
outputs: Record<string, Output>;
|
||||
glowingAgentCards: Record<string, GlowingAgentCard>;
|
||||
expandedSessionIds: string[];
|
||||
@@ -73,7 +71,6 @@ const DashboardCardLayer: React.FC<DashboardCardLayerProps> = ({
|
||||
notes,
|
||||
workflowCards,
|
||||
workflowsHub,
|
||||
configurePanels,
|
||||
outputs,
|
||||
glowingAgentCards,
|
||||
expandedSessionIds,
|
||||
@@ -101,9 +98,6 @@ const DashboardCardLayer: React.FC<DashboardCardLayerProps> = ({
|
||||
onBranch,
|
||||
onMeasuredHeight,
|
||||
}) => {
|
||||
// Ephemeral singleton, not part of the saved layout, so read it straight
|
||||
// from the store rather than threading it through the selector chain.
|
||||
const missedRunsCard = useAppSelector((s) => s.dashboardLayout.missedRunsCard);
|
||||
const dispatch = useAppDispatch();
|
||||
const monitorCard = useAppSelector((s) => s.dashboardLayout.workflowsMonitorCard);
|
||||
const monitorWorkflowId = useAppSelector((s) => s.dashboardLayout.workflowsMonitorId);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { type RefObject } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import { useAppSelector } from '@/shared/hooks';
|
||||
import DashboardToolbar from '../DashboardToolbar';
|
||||
import CanvasControls from '../controls/CanvasControls';
|
||||
import CardSearchPalette from '../controls/CardSearchPalette';
|
||||
@@ -82,7 +81,6 @@ const DashboardOverlays: React.FC<DashboardOverlaysProps> = ({
|
||||
toolbarPrefill,
|
||||
toolbarPrefillMode,
|
||||
}) => {
|
||||
const missedRunsCard = useAppSelector((s) => s.dashboardLayout.missedRunsCard);
|
||||
return (
|
||||
<>
|
||||
{/* Floating bottom toolbar */}
|
||||
@@ -133,7 +131,6 @@ const DashboardOverlays: React.FC<DashboardOverlaysProps> = ({
|
||||
browserCards,
|
||||
workflowCards,
|
||||
workflowsHub,
|
||||
missedRunsCard,
|
||||
}}
|
||||
onMinimapPan={(px, py) => canvas.actions.setState({ panX: px, panY: py, zoom: canvas.zoom })}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useRef, useCallback, useMemo } from 'react';
|
||||
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
|
||||
import type { CardPosition, ViewCardPosition, BrowserCardPosition, WorkflowCardPosition, WorkflowsHubPosition, MissedRunsCardPosition } from '@/shared/state/dashboardLayoutSlice';
|
||||
import type { CardPosition, ViewCardPosition, BrowserCardPosition, WorkflowCardPosition, WorkflowsHubPosition } from '@/shared/state/dashboardLayoutSlice';
|
||||
|
||||
const MINIMAP_W = 200;
|
||||
const MINIMAP_H = 140;
|
||||
@@ -16,7 +16,6 @@ export interface MinimapProps {
|
||||
browserCards: Record<string, BrowserCardPosition>;
|
||||
workflowCards: Record<string, WorkflowCardPosition>;
|
||||
workflowsHub: WorkflowsHubPosition | null;
|
||||
missedRunsCard: MissedRunsCardPosition | null;
|
||||
onPan: (panX: number, panY: number) => void;
|
||||
}
|
||||
|
||||
@@ -25,12 +24,12 @@ interface CardRect {
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
type: 'agent' | 'view' | 'browser' | 'workflow' | 'workflows-hub' | 'missed-runs';
|
||||
type: 'agent' | 'view' | 'browser' | 'workflow' | 'workflows-hub';
|
||||
}
|
||||
|
||||
const Minimap: React.FC<MinimapProps> = ({
|
||||
panX, panY, zoom, viewportRef,
|
||||
cards, viewCards, browserCards, workflowCards, workflowsHub, missedRunsCard,
|
||||
cards, viewCards, browserCards, workflowCards, workflowsHub,
|
||||
onPan,
|
||||
}) => {
|
||||
const c = useClaudeTokens();
|
||||
@@ -60,17 +59,8 @@ const Minimap: React.FC<MinimapProps> = ({
|
||||
type: 'workflows-hub',
|
||||
});
|
||||
}
|
||||
if (missedRunsCard) {
|
||||
result.push({
|
||||
x: missedRunsCard.x,
|
||||
y: missedRunsCard.y,
|
||||
width: missedRunsCard.width,
|
||||
height: missedRunsCard.height,
|
||||
type: 'missed-runs',
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}, [cards, viewCards, browserCards, workflowCards, workflowsHub, missedRunsCard]);
|
||||
}, [cards, viewCards, browserCards, workflowCards, workflowsHub]);
|
||||
|
||||
const layout = useMemo(() => {
|
||||
const vp = viewportRef.current;
|
||||
@@ -159,7 +149,6 @@ const Minimap: React.FC<MinimapProps> = ({
|
||||
case 'browser': return c.status.success;
|
||||
case 'workflow': return c.status.warning;
|
||||
case 'workflows-hub': return c.status.warning;
|
||||
case 'missed-runs': return c.status.warning;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo, type RefObject } from 'react';
|
||||
import type { CardPosition, BrowserCardPosition, WorkflowCardPosition, ConfigurePanelPosition, WorkflowsHubPosition } from '@/shared/state/dashboardLayoutSlice';
|
||||
import type { CardPosition, BrowserCardPosition, WorkflowCardPosition, WorkflowsHubPosition } from '@/shared/state/dashboardLayoutSlice';
|
||||
import type { Workflow, OpenCard } from '@/shared/state/workflowsSlice';
|
||||
import { EXPANDED_CARD_MIN_H, GRID_GAP } from '@/shared/state/dashboardLayoutSlice';
|
||||
import type { AgentSession } from '@/shared/state/agentsSlice';
|
||||
@@ -82,7 +82,6 @@ interface UseTethersArgs {
|
||||
workflowCards: Record<string, WorkflowCardPosition>;
|
||||
workflowItems: Record<string, Workflow>;
|
||||
workflowOpenCards: Record<string, OpenCard>;
|
||||
configurePanels: Record<string, ConfigurePanelPosition>;
|
||||
expandedSessionIds: string[];
|
||||
liveDragInfo: LiveDragInfo | null;
|
||||
measuredHeightsRef: RefObject<Record<string, number>>;
|
||||
@@ -101,7 +100,6 @@ export function useTethers({
|
||||
workflowCards,
|
||||
workflowItems,
|
||||
workflowOpenCards,
|
||||
configurePanels,
|
||||
expandedSessionIds,
|
||||
liveDragInfo,
|
||||
measuredHeightsRef,
|
||||
@@ -390,54 +388,6 @@ export function useTethers({
|
||||
});
|
||||
}
|
||||
|
||||
// Configure-panel tethers: anchor each open configure panel to its workflow card.
|
||||
const configureTethers: Tether[] = [];
|
||||
for (const p of Object.values(configurePanels)) {
|
||||
const wc = workflowCards[p.workflow_id];
|
||||
if (!wc) continue;
|
||||
let srcX = wc.x, srcY = wc.y;
|
||||
let dstX = p.x, dstY = p.y;
|
||||
if (liveDragInfo) {
|
||||
if (liveDragInfo.cardId === p.workflow_id) { srcX += liveDragInfo.dx; srcY += liveDragInfo.dy; }
|
||||
}
|
||||
const wcH = wfHeight(wc);
|
||||
const srcCx = srcX + wc.width / 2;
|
||||
const dstCx = dstX + p.width / 2;
|
||||
const srcAnchors: Anchor[] = [
|
||||
{ x: srcX + wc.width, y: srcY + wcH * 0.5, side: 'right' },
|
||||
{ x: srcX, y: srcY + wcH * 0.5, side: 'left' },
|
||||
{ x: srcCx, y: srcY, side: 'top' },
|
||||
{ x: srcCx, y: srcY + wcH, side: 'bottom' },
|
||||
];
|
||||
const dstAnchors: Anchor[] = [
|
||||
{ x: dstX, y: dstY + p.height * 0.5, side: 'left' },
|
||||
{ x: dstX + p.width, y: dstY + p.height * 0.5, side: 'right' },
|
||||
{ x: dstCx, y: dstY, side: 'top' },
|
||||
{ x: dstCx, y: dstY + p.height, side: 'bottom' },
|
||||
];
|
||||
let bestSrc = srcAnchors[0], bestDst = dstAnchors[0];
|
||||
let bestDist = Infinity;
|
||||
for (const sa of srcAnchors) {
|
||||
for (const da of dstAnchors) {
|
||||
const d = Math.hypot(sa.x - da.x, sa.y - da.y);
|
||||
if (d < bestDist) { bestDist = d; bestSrc = sa; bestDst = da; }
|
||||
}
|
||||
}
|
||||
const x1 = bestSrc.x, y1 = bestSrc.y;
|
||||
const x2 = bestDst.x, y2 = bestDst.y;
|
||||
const pathD = elbowPath(x1, y1, x2, y2);
|
||||
const midX = x1 + (x2 - x1) / 2;
|
||||
const midY = y1 + (y2 - y1) / 2;
|
||||
configureTethers.push({
|
||||
key: `configure-${p.workflow_id}`,
|
||||
path: pathD,
|
||||
labelX: midX,
|
||||
labelY: midY,
|
||||
label: 'Configure',
|
||||
fading: false,
|
||||
});
|
||||
}
|
||||
|
||||
// Run Monitor tether: the Workflows window to its spawned live-run card.
|
||||
// Same border-anchor + elbow math as the sidecar "Watching" arrow.
|
||||
const monitorTethers: Tether[] = [];
|
||||
@@ -470,9 +420,9 @@ export function useTethers({
|
||||
});
|
||||
}
|
||||
|
||||
return [...agentTethers, ...browserTethers, ...workflowTethers, ...configureTethers, ...monitorTethers];
|
||||
return [...agentTethers, ...browserTethers, ...workflowTethers, ...monitorTethers];
|
||||
// measuredHeightsTick re-runs the memo once ResizeObserver reports a new
|
||||
// height after a collapse (the ref read is invisible to the dep checker).
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [glowingAgentCards, glowingBrowserCards, cards, browserCards, workflowCards, workflowItems, workflowOpenCards, configurePanels, expandedSessionIds, liveDragInfo, measuredHeightsTick, sessionList, workflowsHub, workflowsMonitorCard, workflowsMonitorLabel]);
|
||||
}, [glowingAgentCards, glowingBrowserCards, cards, browserCards, workflowCards, workflowItems, workflowOpenCards, expandedSessionIds, liveDragInfo, measuredHeightsTick, sessionList, workflowsHub, workflowsMonitorCard, workflowsMonitorLabel]);
|
||||
}
|
||||
|
||||
@@ -17,12 +17,11 @@ import {
|
||||
clearPendingFocusBrowserId,
|
||||
clearPendingFocusWorkflowId,
|
||||
clearPendingFocusWorkflowsHub,
|
||||
clearPendingFocusMissedRuns,
|
||||
type ViewCardPosition,
|
||||
} from '@/shared/state/dashboardLayoutSlice';
|
||||
import { fetchOutputs, type Output } from '@/shared/state/outputsSlice';
|
||||
import { generateDashboardName } from '@/shared/state/dashboardsSlice';
|
||||
import { fetchWorkflows } from '@/shared/state/workflowsSlice';
|
||||
import { fetchWorkflows, fetchAllRuns, fetchActiveRuns } from '@/shared/state/workflowsSlice';
|
||||
import { fetchMissedRuns } from '@/shared/state/missedRunsSlice';
|
||||
import { dashboardWs } from '@/shared/ws/WebSocketManager';
|
||||
import { initBrowserCommandHandler } from '@/shared/browserCommandHandler';
|
||||
@@ -70,7 +69,6 @@ export function useDashboardLifecycle({
|
||||
const pendingFocusAgentId = useAppSelector((state) => state.tempState.pendingFocusAgentId);
|
||||
const pendingFocusBrowserId = useAppSelector((state) => state.dashboardLayout.pendingFocusBrowserId);
|
||||
const pendingFocusWorkflowId = useAppSelector((state) => state.dashboardLayout.pendingFocusWorkflowId);
|
||||
const pendingFocusMissedRuns = useAppSelector((state) => state.dashboardLayout.pendingFocusMissedRuns);
|
||||
const pendingFocusWorkflowsHub = useAppSelector((state) => state.dashboardLayout.pendingFocusWorkflowsHub);
|
||||
|
||||
// Once per app launch: if scheduled fires elapsed while we were closed, fetch
|
||||
@@ -110,6 +108,12 @@ export function useDashboardLifecycle({
|
||||
const unsubReconnect = dashboardWs.on('dashboard:reconnected', () => {
|
||||
dispatch(fetchSessions({ dashboardId }));
|
||||
dispatch(fetchLayout({ dashboardId, isReconnect: true }));
|
||||
// workflow:run/updated/deleted are global broadcasts that skip the replay
|
||||
// log, so a socket gap drops them: refetch to heal stale "running" cards,
|
||||
// ghost workflows, and missed run history on reconnect.
|
||||
dispatch(fetchWorkflows(dashboardId));
|
||||
dispatch(fetchAllRuns(200));
|
||||
dispatch(fetchActiveRuns());
|
||||
});
|
||||
// DEFERRABLE: history list (for the search palette) and outputs
|
||||
// (for the apps panel) aren't on the first-paint path. Same for the
|
||||
@@ -264,24 +268,6 @@ export function useDashboardLifecycle({
|
||||
}, 200);
|
||||
}, [isActive, pendingFocusWorkflowId, layoutInitialized, dispatch, canvasActions, handleHighlightCard]);
|
||||
|
||||
// Same pan/highlight choreography when the missed-runs card opens from its toast.
|
||||
useEffect(() => {
|
||||
if (!isActive) return;
|
||||
if (!pendingFocusMissedRuns || !layoutInitialized) return;
|
||||
dispatch(clearPendingFocusMissedRuns());
|
||||
setTimeout(() => {
|
||||
const card = store.getState().dashboardLayout.missedRunsCard;
|
||||
if (card) {
|
||||
canvasActions.fitToCards(
|
||||
[{ x: card.x, y: card.y, width: card.width, height: card.height }],
|
||||
1.15,
|
||||
true,
|
||||
);
|
||||
handleHighlightCard('missed-runs');
|
||||
}
|
||||
}, 200);
|
||||
}, [isActive, pendingFocusMissedRuns, layoutInitialized, dispatch, canvasActions, handleHighlightCard]);
|
||||
|
||||
// Pan/zoom to Workflows Hub on Expand; chained rAFs ensure fit runs after the hub div lands at its new coords.
|
||||
useEffect(() => {
|
||||
if (!isActive) return;
|
||||
|
||||
@@ -32,7 +32,7 @@ export function useDashboardController(dashboardId: string, isActive: boolean) {
|
||||
const isElementSelectMode = elementSelectionCtx?.selectMode ?? false;
|
||||
const {
|
||||
dashboardName, sessions, expandedSessionIds, cards, viewCards, browserCards,
|
||||
workflowCards, workflowItems, workflowOpenCards, configurePanels, workflowsHub,
|
||||
workflowCards, workflowItems, workflowOpenCards, workflowsHub,
|
||||
pendingFocusWorkflowId, pendingFocusWorkflowsHub,
|
||||
notes, pendingFocusNoteId, layoutInitialized, persistedExpandedSessionIds,
|
||||
zoomSensitivity, newAgentShortcut, browserHomepage, expandNewChats,
|
||||
@@ -177,7 +177,6 @@ export function useDashboardController(dashboardId: string, isActive: boolean) {
|
||||
viewCards,
|
||||
browserCards,
|
||||
workflowCards,
|
||||
configurePanels,
|
||||
workflowsHub,
|
||||
notes,
|
||||
expandedSessionIds,
|
||||
@@ -296,7 +295,6 @@ export function useDashboardController(dashboardId: string, isActive: boolean) {
|
||||
workflowCards,
|
||||
workflowItems,
|
||||
workflowOpenCards,
|
||||
configurePanels,
|
||||
expandedSessionIds,
|
||||
liveDragInfo,
|
||||
measuredHeightsRef,
|
||||
@@ -310,7 +308,7 @@ export function useDashboardController(dashboardId: string, isActive: boolean) {
|
||||
return {
|
||||
c, dashboardId, dashboardName, canvas, selection, sessions, sessionList,
|
||||
cards, viewCards, browserCards, notes, outputs, glowingAgentCards,
|
||||
workflowCards, workflowsHub, configurePanels,
|
||||
workflowCards, workflowsHub,
|
||||
expandedSessionIds, tethers, highlightedCardId, autoFocusSessionId,
|
||||
focusedCardId, pendingFocusNoteId, multiDragDelta, shakeDirection,
|
||||
neighborDirections, toolbarOpen, searchPaletteOpen, newAgentBounce,
|
||||
|
||||
@@ -25,7 +25,6 @@ export function useDashboardSelectors(dashboardId: string) {
|
||||
return out;
|
||||
}, [allBrowserCards, dashboardId]);
|
||||
const workflowCards = useAppSelector((state) => state.dashboardLayout.workflowCards);
|
||||
const configurePanels = useAppSelector((state) => state.dashboardLayout.configurePanels);
|
||||
const workflowsHub = useAppSelector((state) => state.dashboardLayout.workflowsHub);
|
||||
const pendingFocusWorkflowId = useAppSelector((state) => state.dashboardLayout.pendingFocusWorkflowId);
|
||||
const pendingFocusWorkflowsHub = useAppSelector((state) => state.dashboardLayout.pendingFocusWorkflowsHub);
|
||||
@@ -55,7 +54,6 @@ export function useDashboardSelectors(dashboardId: string) {
|
||||
workflowCards,
|
||||
workflowItems,
|
||||
workflowOpenCards,
|
||||
configurePanels,
|
||||
workflowsHub,
|
||||
pendingFocusWorkflowId,
|
||||
pendingFocusWorkflowsHub,
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
type BrowserCardPosition,
|
||||
type NotePosition,
|
||||
type WorkflowCardPosition,
|
||||
type ConfigurePanelPosition,
|
||||
type WorkflowsHubPosition,
|
||||
} from '@/shared/state/dashboardLayoutSlice';
|
||||
|
||||
@@ -19,7 +18,6 @@ interface UseLayoutSaveArgs {
|
||||
viewCards: Record<string, ViewCardPosition>;
|
||||
browserCards: Record<string, BrowserCardPosition>;
|
||||
workflowCards: Record<string, WorkflowCardPosition>;
|
||||
configurePanels: Record<string, ConfigurePanelPosition>;
|
||||
workflowsHub: WorkflowsHubPosition | null;
|
||||
notes: Record<string, NotePosition>;
|
||||
expandedSessionIds: string[];
|
||||
@@ -38,7 +36,6 @@ export function useLayoutSave({
|
||||
viewCards,
|
||||
browserCards,
|
||||
workflowCards,
|
||||
configurePanels,
|
||||
workflowsHub,
|
||||
notes,
|
||||
expandedSessionIds,
|
||||
@@ -56,7 +53,7 @@ export function useLayoutSave({
|
||||
skipInitialSave.current = false;
|
||||
return;
|
||||
}
|
||||
const payload = { dashboardId, cards, viewCards, browserCards, workflowCards, configurePanels, workflowsHub, notes, expandedSessionIds };
|
||||
const payload = { dashboardId, cards, viewCards, browserCards, workflowCards, workflowsHub, notes, expandedSessionIds };
|
||||
pendingSaveRef.current = payload;
|
||||
if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
|
||||
saveTimerRef.current = setTimeout(() => {
|
||||
@@ -65,7 +62,7 @@ export function useLayoutSave({
|
||||
saveTimerRef.current = null;
|
||||
captureNow();
|
||||
}, 500);
|
||||
}, [isActive, cards, viewCards, browserCards, workflowCards, configurePanels, workflowsHub, notes, expandedSessionIds, layoutInitialized, dashboardId, dispatch, captureNow]);
|
||||
}, [isActive, cards, viewCards, browserCards, workflowCards, workflowsHub, notes, expandedSessionIds, layoutInitialized, dashboardId, dispatch, captureNow]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
||||
@@ -24,14 +24,12 @@ export const DEFAULT_WORKFLOW_CARD_H = 520;
|
||||
// comfortable size automatically.
|
||||
export const DEFAULT_WORKFLOWS_HUB_W = DEFAULT_BROWSER_CARD_W;
|
||||
export const DEFAULT_WORKFLOWS_HUB_H = DEFAULT_BROWSER_CARD_H;
|
||||
export const DEFAULT_MISSED_RUNS_W = 460;
|
||||
export const DEFAULT_MISSED_RUNS_H = 420;
|
||||
export const EXPANDED_CARD_MIN_H = 620;
|
||||
export const GRID_GAP = 24;
|
||||
const GRID_ORIGIN = { x: 40, y: 100 };
|
||||
const GRID_COLS_FALLBACK = 4;
|
||||
|
||||
export type CardType = 'agent' | 'view' | 'browser' | 'note' | 'workflow' | 'workflows-hub' | 'workflows-monitor' | 'missed_runs';
|
||||
export type CardType = 'agent' | 'view' | 'browser' | 'note' | 'workflow' | 'workflows-hub' | 'workflows-monitor';
|
||||
|
||||
export interface CardPosition {
|
||||
session_id: string;
|
||||
@@ -93,16 +91,6 @@ export interface WorkflowsHubPosition {
|
||||
zOrder: number;
|
||||
}
|
||||
|
||||
// Ephemeral launch-time card listing scheduled fires missed while the app was
|
||||
// closed. Singleton like workflowsHub, but deliberately NOT persisted to the
|
||||
// saved layout: the launch hook decides each session whether to show it.
|
||||
export interface MissedRunsCardPosition {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
zOrder: number;
|
||||
}
|
||||
|
||||
export type NoteColor = 'yellow' | 'pink' | 'blue' | 'green' | 'purple' | 'gray';
|
||||
|
||||
@@ -120,22 +108,12 @@ export interface NotePosition {
|
||||
export const DEFAULT_NOTE_W = 240;
|
||||
export const DEFAULT_NOTE_H = 200;
|
||||
|
||||
export interface ConfigurePanelPosition {
|
||||
workflow_id: string;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface DashboardLayoutState {
|
||||
cards: Record<string, CardPosition>;
|
||||
viewCards: Record<string, ViewCardPosition>;
|
||||
browserCards: Record<string, BrowserCardPosition>;
|
||||
workflowCards: Record<string, WorkflowCardPosition>;
|
||||
configurePanels: Record<string, ConfigurePanelPosition>;
|
||||
workflowsHub: WorkflowsHubPosition | null;
|
||||
missedRunsCard: MissedRunsCardPosition | null;
|
||||
notes: Record<string, NotePosition>;
|
||||
closedCardPositions: Record<string, CardPosition>;
|
||||
glowingBrowserCards: Record<string, { sourceId: string; fading: boolean; label?: string }>;
|
||||
@@ -154,8 +132,6 @@ export interface DashboardLayoutState {
|
||||
/** Transient: id of the view card the user has clicked into; preload stops forwarding canvas gestures while set. */
|
||||
activeViewCardId: string | null;
|
||||
pendingFocusWorkflowId: string | null;
|
||||
/** Transient: signals Dashboard to pan/zoom to the missed-runs card on open. */
|
||||
pendingFocusMissedRuns: boolean;
|
||||
/** Transient: signals Dashboard to pan/zoom to the singleton Workflows Hub on open. */
|
||||
pendingFocusWorkflowsHub: boolean;
|
||||
/** Transient deep-link target: the Workflows card jumps to this workflow's detail on open, then clears it. */
|
||||
@@ -183,9 +159,7 @@ const initialState: DashboardLayoutState = {
|
||||
viewCards: {},
|
||||
browserCards: {},
|
||||
workflowCards: {},
|
||||
configurePanels: {},
|
||||
workflowsHub: null,
|
||||
missedRunsCard: null,
|
||||
notes: {},
|
||||
closedCardPositions: {},
|
||||
glowingBrowserCards: {},
|
||||
@@ -200,7 +174,6 @@ const initialState: DashboardLayoutState = {
|
||||
endingBrowserCards: {},
|
||||
activeViewCardId: null,
|
||||
pendingFocusWorkflowId: null,
|
||||
pendingFocusMissedRuns: false,
|
||||
pendingFocusWorkflowsHub: false,
|
||||
workflowsAppTarget: null,
|
||||
workflowsMonitorId: null,
|
||||
@@ -214,7 +187,6 @@ interface LayoutPayload {
|
||||
viewCards: Record<string, ViewCardPosition>;
|
||||
browserCards: Record<string, BrowserCardPosition>;
|
||||
workflowCards: Record<string, WorkflowCardPosition>;
|
||||
configurePanels: Record<string, ConfigurePanelPosition>;
|
||||
workflowsHub: WorkflowsHubPosition | null;
|
||||
notes: Record<string, NotePosition>;
|
||||
expandedSessionIds: string[];
|
||||
@@ -253,7 +225,6 @@ export const fetchLayout = createAsyncThunk(
|
||||
viewCards: (layout.view_cards ?? {}) as Record<string, ViewCardPosition>,
|
||||
browserCards: browserCards as Record<string, BrowserCardPosition>,
|
||||
workflowCards: (layout.workflow_cards ?? {}) as Record<string, WorkflowCardPosition>,
|
||||
configurePanels: (layout.configure_panels ?? {}) as Record<string, ConfigurePanelPosition>,
|
||||
workflowsHub: (layout.workflows_hub ?? null) as WorkflowsHubPosition | null,
|
||||
notes: (layout.notes ?? {}) as Record<string, NotePosition>,
|
||||
expandedSessionIds: (layout.expanded_session_ids ?? []) as string[],
|
||||
@@ -277,7 +248,6 @@ export const saveLayout = createAsyncThunk(
|
||||
view_cards: payload.viewCards,
|
||||
browser_cards: payload.browserCards,
|
||||
workflow_cards: payload.workflowCards,
|
||||
configure_panels: payload.configurePanels,
|
||||
workflows_hub: payload.workflowsHub,
|
||||
notes: payload.notes,
|
||||
expanded_session_ids: payload.expandedSessionIds,
|
||||
@@ -321,9 +291,6 @@ function collectOccupiedRects(
|
||||
if (state.workflowsHub) {
|
||||
rects.push({ x: state.workflowsHub.x, y: state.workflowsHub.y, w: state.workflowsHub.width, h: state.workflowsHub.height });
|
||||
}
|
||||
if (state.missedRunsCard) {
|
||||
rects.push({ x: state.missedRunsCard.x, y: state.missedRunsCard.y, w: state.missedRunsCard.width, h: state.missedRunsCard.height });
|
||||
}
|
||||
for (const n of Object.values(state.notes)) {
|
||||
rects.push({ x: n.x, y: n.y, w: n.width, h: n.height });
|
||||
}
|
||||
@@ -520,14 +487,12 @@ const dashboardLayoutSlice = createSlice({
|
||||
for (const n of Object.values(state.notes)) tally(n.zOrder);
|
||||
if (state.workflowsHub) tally(state.workflowsHub.zOrder);
|
||||
if (state.workflowsMonitorCard) tally(state.workflowsMonitorCard.zOrder);
|
||||
if (state.missedRunsCard) tally(state.missedRunsCard.zOrder);
|
||||
if (type === 'agent') currentZ = state.cards[id]?.zOrder ?? 0;
|
||||
else if (type === 'view') currentZ = state.viewCards[id]?.zOrder ?? 0;
|
||||
else if (type === 'note') currentZ = state.notes[id]?.zOrder ?? 0;
|
||||
else if (type === 'workflow') currentZ = state.workflowCards[id]?.zOrder ?? 0;
|
||||
else if (type === 'workflows-hub') currentZ = state.workflowsHub?.zOrder ?? 0;
|
||||
else if (type === 'workflows-monitor') currentZ = state.workflowsMonitorCard?.zOrder ?? 0;
|
||||
else if (type === 'missed_runs') currentZ = state.missedRunsCard?.zOrder ?? 0;
|
||||
else currentZ = state.browserCards[id]?.zOrder ?? 0;
|
||||
if (currentZ >= maxZ) return; // Already on top: no-op.
|
||||
|
||||
@@ -548,8 +513,6 @@ const dashboardLayoutSlice = createSlice({
|
||||
if (state.workflowsHub) state.workflowsHub.zOrder = z;
|
||||
} else if (type === 'workflows-monitor') {
|
||||
if (state.workflowsMonitorCard) state.workflowsMonitorCard.zOrder = z;
|
||||
} else if (type === 'missed_runs') {
|
||||
if (state.missedRunsCard) state.missedRunsCard.zOrder = z;
|
||||
} else {
|
||||
const card = state.browserCards[id];
|
||||
if (card) card.zOrder = z;
|
||||
@@ -607,7 +570,8 @@ const dashboardLayoutSlice = createSlice({
|
||||
const bCards = Object.values(state.browserCards);
|
||||
const wCards = Object.values(state.workflowCards);
|
||||
const hub = state.workflowsHub;
|
||||
const total = agentCards.length + viewCards.length + bCards.length + wCards.length + (hub ? 1 : 0);
|
||||
const mon = state.workflowsMonitorCard;
|
||||
const total = agentCards.length + viewCards.length + bCards.length + wCards.length + (hub ? 1 : 0) + (mon ? 1 : 0);
|
||||
if (total === 0) return;
|
||||
|
||||
const allItems = [
|
||||
@@ -616,6 +580,7 @@ const dashboardLayoutSlice = createSlice({
|
||||
...bCards.map((c) => ({ kind: 'browser' as const, id: c.browser_id, x: c.x, y: c.y, storedW: c.width, storedH: c.height })),
|
||||
...wCards.map((c) => ({ kind: 'workflow' as const, id: c.workflow_id, x: c.x, y: c.y, storedW: c.width, storedH: c.height })),
|
||||
...(hub ? [{ kind: 'workflows-hub' as const, id: 'workflows-hub', x: hub.x, y: hub.y, storedW: hub.width, storedH: hub.height }] : []),
|
||||
...(mon ? [{ kind: 'workflows-monitor' as const, id: 'workflows-monitor', x: mon.x, y: mon.y, storedW: mon.width, storedH: mon.height }] : []),
|
||||
];
|
||||
allItems.sort((a, b) => a.y - b.y || a.x - b.x);
|
||||
|
||||
@@ -645,6 +610,8 @@ const dashboardLayoutSlice = createSlice({
|
||||
if (card) { card.x = pos.x; card.y = pos.y; }
|
||||
} else if (item.kind === 'workflows-hub') {
|
||||
if (state.workflowsHub) { state.workflowsHub.x = pos.x; state.workflowsHub.y = pos.y; }
|
||||
} else if (item.kind === 'workflows-monitor') {
|
||||
if (state.workflowsMonitorCard) { state.workflowsMonitorCard.x = pos.x; state.workflowsMonitorCard.y = pos.y; }
|
||||
} else {
|
||||
const card = state.browserCards[item.id];
|
||||
if (card) { card.x = pos.x; card.y = pos.y; }
|
||||
@@ -888,65 +855,9 @@ const dashboardLayoutSlice = createSlice({
|
||||
if (!card) return;
|
||||
delete state.workflowCards[oldId];
|
||||
state.workflowCards[newId] = { ...card, workflow_id: newId };
|
||||
// Carry any open Action-Library panel along with the rekey so the
|
||||
// popout doesn't disappear when a draft is saved.
|
||||
const panel = state.configurePanels[oldId];
|
||||
if (panel) {
|
||||
delete state.configurePanels[oldId];
|
||||
state.configurePanels[newId] = { ...panel, workflow_id: newId };
|
||||
}
|
||||
if (state.pendingFocusWorkflowId === oldId) state.pendingFocusWorkflowId = newId;
|
||||
},
|
||||
|
||||
openConfigurePanel(
|
||||
state,
|
||||
action: PayloadAction<{ workflowId: string }>,
|
||||
) {
|
||||
const { workflowId } = action.payload;
|
||||
// Anchor the panel just to the right of the workflow card.
|
||||
const wfCard = state.workflowCards[workflowId];
|
||||
const baseX = wfCard ? wfCard.x + wfCard.width + GRID_GAP * 6 : 600;
|
||||
const baseY = wfCard ? wfCard.y : 200;
|
||||
const existing = state.configurePanels[workflowId];
|
||||
if (existing) {
|
||||
existing.x = baseX;
|
||||
existing.y = baseY;
|
||||
return;
|
||||
}
|
||||
state.configurePanels[workflowId] = {
|
||||
workflow_id: workflowId,
|
||||
x: baseX,
|
||||
y: baseY,
|
||||
width: 580,
|
||||
height: 600,
|
||||
};
|
||||
},
|
||||
|
||||
setConfigurePanelPosition(
|
||||
state,
|
||||
action: PayloadAction<{ workflowId: string; x: number; y: number }>,
|
||||
) {
|
||||
const { workflowId, x, y } = action.payload;
|
||||
const p = state.configurePanels[workflowId];
|
||||
if (p) { p.x = x; p.y = y; }
|
||||
},
|
||||
|
||||
setConfigurePanelSize(
|
||||
state,
|
||||
action: PayloadAction<{ workflowId: string; width: number; height: number }>,
|
||||
) {
|
||||
const { workflowId, width, height } = action.payload;
|
||||
const p = state.configurePanels[workflowId];
|
||||
if (p) {
|
||||
p.width = Math.max(360, width);
|
||||
p.height = Math.max(280, height);
|
||||
}
|
||||
},
|
||||
|
||||
closeConfigurePanel(state, action: PayloadAction<string>) {
|
||||
delete state.configurePanels[action.payload];
|
||||
},
|
||||
|
||||
clearPendingFocusWorkflowId(state) {
|
||||
state.pendingFocusWorkflowId = null;
|
||||
},
|
||||
@@ -973,37 +884,6 @@ const dashboardLayoutSlice = createSlice({
|
||||
state.pendingFocusWorkflowsHub = false;
|
||||
},
|
||||
|
||||
openMissedRunsCard(state, action: PayloadAction<{ expandedSessionIds?: string[] } | undefined>) {
|
||||
state.pendingFocusMissedRuns = true;
|
||||
if (state.missedRunsCard) {
|
||||
state.missedRunsCard.zOrder = state.nextZOrder++;
|
||||
return;
|
||||
}
|
||||
const rects = collectOccupiedRects(state, action.payload?.expandedSessionIds);
|
||||
const pos = findOpenGridCell(rects, DEFAULT_MISSED_RUNS_W, DEFAULT_MISSED_RUNS_H);
|
||||
state.missedRunsCard = {
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
width: DEFAULT_MISSED_RUNS_W,
|
||||
height: DEFAULT_MISSED_RUNS_H,
|
||||
zOrder: state.nextZOrder++,
|
||||
};
|
||||
},
|
||||
|
||||
clearPendingFocusMissedRuns(state) {
|
||||
state.pendingFocusMissedRuns = false;
|
||||
},
|
||||
|
||||
closeMissedRunsCard(state) {
|
||||
state.missedRunsCard = null;
|
||||
},
|
||||
|
||||
setMissedRunsCardPosition(state, action: PayloadAction<{ x: number; y: number }>) {
|
||||
if (!state.missedRunsCard) return;
|
||||
state.missedRunsCard.x = action.payload.x;
|
||||
state.missedRunsCard.y = action.payload.y;
|
||||
},
|
||||
|
||||
closeWorkflowsHub(state) {
|
||||
state.workflowsHub = null;
|
||||
},
|
||||
@@ -1281,11 +1161,6 @@ const dashboardLayoutSlice = createSlice({
|
||||
state.workflowsHub.x += dx;
|
||||
state.workflowsHub.y += dy;
|
||||
}
|
||||
} else if (item.type === 'missed_runs') {
|
||||
if (state.missedRunsCard) {
|
||||
state.missedRunsCard.x += dx;
|
||||
state.missedRunsCard.y += dy;
|
||||
}
|
||||
} else {
|
||||
const card = state.browserCards[item.id];
|
||||
if (card) {
|
||||
@@ -1414,9 +1289,7 @@ const dashboardLayoutSlice = createSlice({
|
||||
state.viewCards = {};
|
||||
state.browserCards = {};
|
||||
state.workflowCards = {};
|
||||
state.configurePanels = {};
|
||||
state.workflowsHub = null;
|
||||
state.missedRunsCard = null;
|
||||
state.notes = {};
|
||||
state.closedCardPositions = {};
|
||||
state.glowingBrowserCards = {};
|
||||
@@ -1428,7 +1301,6 @@ const dashboardLayoutSlice = createSlice({
|
||||
state.suspendedBrowserCards = {};
|
||||
state.endingBrowserCards = {};
|
||||
state.pendingFocusWorkflowId = null;
|
||||
state.pendingFocusMissedRuns = false;
|
||||
},
|
||||
|
||||
},
|
||||
@@ -1456,7 +1328,6 @@ const dashboardLayoutSlice = createSlice({
|
||||
card.dashboard_id = ownerDashboardId;
|
||||
}
|
||||
state.workflowCards = action.payload.workflowCards || {};
|
||||
state.configurePanels = action.payload.configurePanels || {};
|
||||
state.workflowsHub = action.payload.workflowsHub || null;
|
||||
state.notes = action.payload.notes || {};
|
||||
// Cards boot parked (no guest process, title placeholder); the suspend
|
||||
@@ -1475,7 +1346,6 @@ const dashboardLayoutSlice = createSlice({
|
||||
if (!card.dashboard_id) card.dashboard_id = ownerDashboardId;
|
||||
}
|
||||
addMissingCards(state.workflowCards, action.payload.workflowCards || {}, occupied);
|
||||
addMissingCards(state.configurePanels, action.payload.configurePanels || {}, occupied);
|
||||
if (!state.workflowsHub && action.payload.workflowsHub) state.workflowsHub = action.payload.workflowsHub;
|
||||
addMissingCards(state.notes, action.payload.notes || {}, occupied);
|
||||
}
|
||||
@@ -1520,7 +1390,6 @@ const dashboardLayoutSlice = createSlice({
|
||||
.addCase(deleteWorkflowFulfilledAction, (state, action) => {
|
||||
const id = action.payload;
|
||||
if (id && state.workflowCards[id]) delete state.workflowCards[id];
|
||||
if (id && state.configurePanels[id]) delete state.configurePanels[id];
|
||||
})
|
||||
.addCase(launchAndSendFirstMessage.fulfilled, (state, action) => {
|
||||
const { draftId, session } = action.payload;
|
||||
@@ -1579,10 +1448,6 @@ export const {
|
||||
setWorkflowCardSize,
|
||||
removeWorkflowCard,
|
||||
rekeyWorkflowCard,
|
||||
openConfigurePanel,
|
||||
closeConfigurePanel,
|
||||
setConfigurePanelPosition,
|
||||
setConfigurePanelSize,
|
||||
clearPendingFocusWorkflowId,
|
||||
openWorkflowsHub,
|
||||
closeWorkflowsHub,
|
||||
@@ -1597,10 +1462,6 @@ export const {
|
||||
setWorkflowsHubPosition,
|
||||
setWorkflowsHubSize,
|
||||
clearPendingFocusWorkflowsHub,
|
||||
openMissedRunsCard,
|
||||
clearPendingFocusMissedRuns,
|
||||
closeMissedRunsCard,
|
||||
setMissedRunsCardPosition,
|
||||
addNote,
|
||||
setNotePosition,
|
||||
setNoteSize,
|
||||
|
||||
Reference in New Issue
Block a user