import { useState, useEffect, Suspense, lazy } from 'react' import WorkflowBoard from './components/WorkflowBoard' import SecretsZone, { MOCK_SECTIONS } from './components/SecretsZone' import WorkflowBuilder from './components/WorkflowBuilder' import GatesDrawer from './components/GatesDrawer' import GateDrawer from './components/GateDrawer' import LogDrawer from './components/LogDrawer' import CommandPalette from './components/CommandPalette' import TierGate from './components/TierGate' import InfraRegistry from './components/InfraRegistry' import { ToastProvider, useToast } from './components/ToastProvider' import { useWorkflows } from './hooks/useWorkflows' import { useWebSocket } from './hooks/useWebSocket' import { useBrainStore } from './store/brain.store' import { useTier } from './hooks/useTier' const CosmosView = lazy(() => import('./components/cosmos/CosmosView')) const WorkspaceView = lazy(() => import('./components/workspace/WorkspaceView')) type ActiveView = 'workflows' | 'builder' | 'secrets' | 'infra' | 'cosmos' | 'workspace' interface NavItem { id: ActiveView icon: string label: string separator?: boolean } interface PendingGate { workflowId: string stepId: string stepLabel: string } const NAV_ITEMS: NavItem[] = [ { id: 'cosmos', icon: '🌌', label: 'Cosmos' }, ] function AppInner() { const { addToast } = useToast() // Detect URL path for direct routing (/ui/docs → docs view) const initialView = (): ActiveView => { const path = window.location.pathname // /docs → redirige vers docs.html (page standalone) if (path.includes('/cosmos')) return 'cosmos' if (path.includes('/workspace')) return 'workspace' return 'workflows' } const [activeView, setActiveView] = useState(initialView) const [pendingGate, setPendingGate] = useState(null) const [gateDrawer, setGateDrawer] = useState<{ open: boolean; workflowId: string | null; stepId: string | null }>({ open: false, workflowId: null, stepId: null, }) const [logsProject, setLogsProject] = useState(null) const [paletteOpen, setPaletteOpen] = useState(false) // Sync URL with active view const handleViewChange = (view: ActiveView) => { setActiveView(view) const base = import.meta.env.BASE_URL || '/ui/' const slug = view === 'workflows' ? '' : view window.history.replaceState(null, '', `${base}${slug}`) } const { workflows, wsStatus } = useWorkflows() useWebSocket(addToast) const storeWorkflows = useBrainStore((s) => s.workflows) const { hasFeature, tierInfo } = useTier() useEffect(() => { const handler = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault() setPaletteOpen(true) } if ((e.metaKey || e.ctrlKey) && e.key === 'l') { e.preventDefault() setLogsProject((prev) => (prev ? null : (storeWorkflows[0]?.id ?? null))) } } window.addEventListener('keydown', handler) return () => window.removeEventListener('keydown', handler) }, [storeWorkflows]) const handleGateApprove = (workflowId: string, stepId: string) => { const wf = storeWorkflows.find((w) => w.id === workflowId) const step = wf?.steps.find((s) => s.id === stepId) const label = step?.label ?? stepId setPendingGate({ workflowId, stepId, stepLabel: label }) setGateDrawer({ open: true, workflowId, stepId }) } const handleSecretSave = (section: string, key: string, value: string) => { console.log(`secret:save — ${section}.${key} (${value.length} chars)`) // TODO: appel API brain } return (
{/* Sidebar */} {/* Main content */}
{activeView === 'workflows' && ( setLogsProject(wfId)} /> )} {activeView === 'builder' && ( )} {activeView === 'secrets' && ( )} {activeView === 'infra' && ( )} {activeView === 'cosmos' && (
Chargement Cosmos...
}>
)} {activeView === 'workspace' && ( Chargement Workspace... }> )} {/* GatesDrawer — affiché si gate en attente */} {pendingGate && ( setPendingGate(null)} onReject={async () => setPendingGate(null)} onClose={() => setPendingGate(null)} /> )} {/* LogDrawer — slide-in depuis la droite */} setLogsProject(null)} /> {/* GateDrawer — approbation workflow SuperOAuth */} setGateDrawer((prev) => ({ ...prev, open: false }))} /> {/* CommandPalette — Cmd+K */} {paletteOpen && ( setPaletteOpen(false)} onNavigate={(view) => { handleViewChange(view as ActiveView); setPaletteOpen(false) }} /> )} ) } export default function App() { return ( ) }