diff --git a/frontend/src/app/pages/Dashboard/desktop/DesktopDock.tsx b/frontend/src/app/pages/Dashboard/desktop/DesktopDock.tsx index ab7c1a13..b458a117 100644 --- a/frontend/src/app/pages/Dashboard/desktop/DesktopDock.tsx +++ b/frontend/src/app/pages/Dashboard/desktop/DesktopDock.tsx @@ -7,17 +7,13 @@ import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline'; import EventRepeatIcon from '@mui/icons-material/EventRepeat'; import StickyNote2OutlinedIcon from '@mui/icons-material/StickyNote2Outlined'; import HistoryIcon from '@mui/icons-material/History'; -import { pickIcon } from '../canvas/DashboardGlyph'; -import { MessageCircle } from 'lucide-react'; import { openWorkflowsApp } from '@/shared/state/dashboardLayoutSlice'; import SettingsIcon from '@mui/icons-material/Settings'; import AppsRoundedIcon from '@mui/icons-material/AppsRounded'; -import EditNoteIcon from '@mui/icons-material/EditNote'; -import CalendarMonthIcon from '@mui/icons-material/CalendarMonth'; import { useAppDispatch } from '@/shared/hooks'; import { openSettingsModal } from '@/shared/state/settingsSlice'; import { getWebview } from '@/shared/browserRegistry'; -import { displayChatTitle } from '@/shared/state/sessionDisplay'; +import { buildDockEntries, CardRect, DockEntry } from './dockEntries'; import type { AgentSession } from '@/shared/state/agentsSlice'; import type { CardPosition, @@ -28,25 +24,6 @@ import type { } from '@/shared/state/dashboardLayoutSlice'; import type { Output } from '@/shared/state/outputsSlice'; -interface CardRect { - x: number; - y: number; - width: number; - height: number; -} - -interface DockEntry { - id: string; - label: string; - rect: CardRect; - tileBg: string; - icon: React.ReactNode; - faviconUrl?: string; - thumbnail?: string | null; - browserId?: string; - snippet?: string; -} - interface DesktopDockProps { sessions: Record; cards: Record; @@ -66,21 +43,6 @@ interface DesktopDockProps { const TILE = 30; const PREVIEW_W = 190; -// Frames show a colorful per-card dock, not uniform tiles; hues rotate by name so two agents rarely match. -const AGENT_TILE_HUES = [ - 'linear-gradient(135deg, #4a7dd6, #2b4fa8)', - 'linear-gradient(135deg, #8a5bd6, #5b34a8)', - 'linear-gradient(135deg, #3aa88f, #1f7a64)', - 'linear-gradient(135deg, #d6754a, #a8492b)', - 'linear-gradient(135deg, #c94a7d, #96305c)', -]; - -function hueFor(name: string): string { - let h = 0; - for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) | 0; - return AGENT_TILE_HUES[Math.abs(h) % AGENT_TILE_HUES.length]; -} - /** Left-edge desktop dock: one tile per open card, hover previews, click focuses the window. */ function DesktopDock({ sessions, @@ -133,72 +95,10 @@ function DesktopDock({ const [liveShot, setLiveShot] = useState<{ id: string; dataUrl: string } | null>(null); const hoverTimer = useRef(null); - const entries = useMemo(() => { - const list: DockEntry[] = []; - for (const card of Object.values(cards)) { - const session = sessions[card.session_id]; - if (!session) continue; - const title = displayChatTitle(session); - const ChatIcon = pickIcon(title) || MessageCircle; - list.push({ - id: card.session_id, - label: title, - rect: card, - tileBg: hueFor(title), - icon: , - snippet: session.turn_label?.label || undefined, - }); - } - for (const bc of Object.values(browserCards)) { - const activeTab = bc.tabs.find((t) => t.id === bc.activeTabId) || bc.tabs[0]; - list.push({ - id: bc.browser_id, - label: activeTab?.title || 'Browser', - rect: bc, - tileBg: 'linear-gradient(135deg, #4f9fe8, #2f6ed4)', - icon: , - faviconUrl: activeTab?.favicon, - browserId: bc.browser_id, - }); - } - for (const [cardKey, vc] of Object.entries(viewCards)) { - const output = outputs[vc.output_id]; - const appName = output?.name || 'App'; - list.push({ - id: cardKey, - label: appName, - rect: vc, - tileBg: 'linear-gradient(135deg, #ef9552, #d96a2b)', - icon: ( - - {appName.charAt(0).toUpperCase()} - - ), - thumbnail: output?.thumbnail, - }); - } - for (const note of Object.values(notes)) { - const firstLine = (note.content || '').split('\n')[0].trim(); - list.push({ - id: note.note_id, - label: firstLine || 'Note', - rect: note, - tileBg: 'linear-gradient(135deg, #f2d270, #e0b23e)', - icon: , - snippet: (note.content || '').slice(0, 140), - }); - } - for (const [cardKey, wf] of Object.entries(workflowCards)) { - list.push({ - id: cardKey, - label: 'Workflow', - rect: wf, - tileBg: 'linear-gradient(135deg, #ef7a70, #d94f45)', - icon: , - }); - } - return list; - }, [sessions, cards, viewCards, browserCards, notes, workflowCards, outputs]); + const entries = useMemo( + () => buildDockEntries({ sessions, cards, viewCards, browserCards, notes, workflowCards, outputs }), + [sessions, cards, viewCards, browserCards, notes, workflowCards, outputs], + ); const beginHover = useCallback( (entry: DockEntry, target: HTMLElement) => { @@ -304,72 +204,40 @@ function DesktopDock({ {entries.length > 0 && ( )} - {/* The og toolbar's actions, dock-resident: chat, browser, workflow, note, history. */} + {/* The og toolbar's actions, dock-resident: chat, browser, workflow, note, history, then settings + apps below their own divider. */} {([ { label: 'New chat', icon: , act: onNewAgent }, { label: 'New browser', icon: , act: onAddBrowser }, { label: 'Workflows', icon: , act: () => dispatch(openWorkflowsApp()) }, { label: 'New note', icon: , act: onAddNote }, { label: 'History', icon: , act: () => window.dispatchEvent(new CustomEvent('openswarm:open-history')) }, - ] as const).map((a) => ( - - - {a.icon} - - + { label: 'Settings', icon: , act: () => dispatch(openSettingsModal(undefined)), divider: true }, + { label: 'Applications', icon: , act: onApplications, bg: 'linear-gradient(135deg, #3d3d46, #232329)' }, + ] as { label: string; icon: React.ReactNode; act: () => void; divider?: boolean; bg?: string }[]).map((a) => ( + + {a.divider && } + + + {a.icon} + + + ))} - - dispatch(openSettingsModal(undefined))} - onMouseEnter={endHover} - sx={{ - width: TILE, - height: TILE, - borderRadius: '12px', - background: 'linear-gradient(135deg, #5a5a62, #34343c)', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - cursor: 'pointer', - flexShrink: 0, - }} - > - - - - - {hoveredEntry && ( ; + cards: Record; + viewCards: Record; + browserCards: Record; + notes: Record; + workflowCards: Record; + outputs: Record; +} + +// Frames show a colorful per-card dock, not uniform tiles; hues rotate by name so two agents rarely match. +const AGENT_TILE_HUES = [ + 'linear-gradient(135deg, #4a7dd6, #2b4fa8)', + 'linear-gradient(135deg, #8a5bd6, #5b34a8)', + 'linear-gradient(135deg, #3aa88f, #1f7a64)', + 'linear-gradient(135deg, #d6754a, #a8492b)', + 'linear-gradient(135deg, #c94a7d, #96305c)', +]; + +function hueFor(name: string): string { + let h = 0; + for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) | 0; + return AGENT_TILE_HUES[Math.abs(h) % AGENT_TILE_HUES.length]; +} + +export function buildDockEntries({ sessions, cards, viewCards, browserCards, notes, workflowCards, outputs }: DockSlices): DockEntry[] { + const list: DockEntry[] = []; + for (const card of Object.values(cards)) { + const session = sessions[card.session_id]; + if (!session) continue; + const title = displayChatTitle(session); + const ChatIcon = pickIcon(title) || MessageCircle; + list.push({ + id: card.session_id, + label: title, + rect: card, + tileBg: hueFor(title), + icon: , + snippet: session.turn_label?.label || undefined, + }); + } + for (const bc of Object.values(browserCards)) { + const activeTab = bc.tabs.find((t) => t.id === bc.activeTabId) || bc.tabs[0]; + list.push({ + id: bc.browser_id, + label: activeTab?.title || 'Browser', + rect: bc, + tileBg: 'linear-gradient(135deg, #4f9fe8, #2f6ed4)', + icon: , + faviconUrl: activeTab?.favicon, + browserId: bc.browser_id, + }); + } + for (const [cardKey, vc] of Object.entries(viewCards)) { + const output = outputs[vc.output_id]; + const appName = output?.name || 'App'; + list.push({ + id: cardKey, + label: appName, + rect: vc, + tileBg: 'linear-gradient(135deg, #ef9552, #d96a2b)', + icon: ( + + {appName.charAt(0).toUpperCase()} + + ), + thumbnail: output?.thumbnail, + }); + } + for (const note of Object.values(notes)) { + const firstLine = (note.content || '').split('\n')[0].trim(); + list.push({ + id: note.note_id, + label: firstLine || 'Note', + rect: note, + tileBg: 'linear-gradient(135deg, #f2d270, #e0b23e)', + icon: , + snippet: (note.content || '').slice(0, 140), + }); + } + for (const [cardKey, wf] of Object.entries(workflowCards)) { + list.push({ + id: cardKey, + label: 'Workflow', + rect: wf, + tileBg: 'linear-gradient(135deg, #ef7a70, #d94f45)', + icon: , + }); + } + return list; +}