import { useEffect, useCallback } from 'react'; import { createPortal } from 'react-dom'; import { useCommandPalette, type PaletteScope } from './use-command-palette'; import { usePaletteSearch, type PaletteResult } from './use-palette-search'; import { useExecutionSearch } from '../../pages/executions/use-execution-search'; import { PaletteInput } from './PaletteInput'; import { ScopeTabs } from './ScopeTabs'; import { ResultsList } from './ResultsList'; import { PaletteFooter } from './PaletteFooter'; import type { ExecutionSummary, AgentInstance } from '../../api/schema'; import styles from './CommandPalette.module.css'; const SCOPES: PaletteScope[] = ['all', 'executions', 'agents']; export function CommandPalette() { const { isOpen, close, scope, setScope, selectedIndex, setSelectedIndex, reset, filters } = useCommandPalette(); const { results, executionCount, agentCount, isLoading } = usePaletteSearch(); const execSearch = useExecutionSearch(); const handleSelect = useCallback( (result: PaletteResult) => { if (result.type === 'execution') { const exec = result.data as ExecutionSummary; execSearch.setStatus(['COMPLETED', 'FAILED', 'RUNNING']); execSearch.setText(exec.executionId); execSearch.setRouteId(''); execSearch.setAgentId(''); execSearch.setProcessorType(''); } else if (result.type === 'agent') { const agent = result.data as AgentInstance; execSearch.setStatus(['COMPLETED', 'FAILED', 'RUNNING']); execSearch.setAgentId(agent.agentId); execSearch.setText(''); execSearch.setRouteId(''); execSearch.setProcessorType(''); } // Apply any active palette filters to the execution search for (const f of filters) { if (f.key === 'status') execSearch.setStatus([f.value.toUpperCase()]); if (f.key === 'route') execSearch.setRouteId(f.value); if (f.key === 'agent') execSearch.setAgentId(f.value); if (f.key === 'processor') execSearch.setProcessorType(f.value); } close(); reset(); }, [close, reset, execSearch, filters], ); const handleKeyDown = useCallback( (e: KeyboardEvent) => { if (!isOpen) return; switch (e.key) { case 'Escape': e.preventDefault(); close(); reset(); break; case 'ArrowDown': e.preventDefault(); setSelectedIndex( results.length > 0 ? (selectedIndex + 1) % results.length : 0, ); break; case 'ArrowUp': e.preventDefault(); setSelectedIndex( results.length > 0 ? (selectedIndex - 1 + results.length) % results.length : 0, ); break; case 'Enter': e.preventDefault(); if (results[selectedIndex]) { handleSelect(results[selectedIndex]); } break; case 'Tab': e.preventDefault(); const idx = SCOPES.indexOf(scope); setScope(SCOPES[(idx + 1) % SCOPES.length]); break; } }, [isOpen, close, reset, selectedIndex, setSelectedIndex, results, handleSelect, scope, setScope], ); // Global Cmd+K / Ctrl+K listener useEffect(() => { function onKeyDown(e: KeyboardEvent) { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); const store = useCommandPalette.getState(); if (store.isOpen) { store.close(); store.reset(); } else { store.open(); } } } document.addEventListener('keydown', onKeyDown); return () => document.removeEventListener('keydown', onKeyDown); }, []); // Keyboard handling when open useEffect(() => { document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [handleKeyDown]); if (!isOpen) return null; return createPortal(
{ if (e.target === e.currentTarget) { close(); reset(); } }}>
, document.body, ); }