'use client'; import { useState, useEffect, useCallback } from 'react'; import { usePathname, useRouter } from 'next/navigation'; import { motion, AnimatePresence } from 'framer-motion'; import { getApiBaseUrl } from '@/lib/api'; import { MessageSquare, Globe, BarChart3, Layers, Activity, Zap, Shield, Terminal, Settings, Plus, ChevronLeft, ChevronRight, Menu, X, Brain, Mic } from 'lucide-react'; const navSections = [ { label: 'Main', items: [ { path: '/', label: 'Chat', icon: MessageSquare }, { path: '/intel', label: 'Intel', icon: Globe }, { path: '/markets', label: 'Markets', icon: BarChart3 }, { path: '/workspace', label: 'Workspace', icon: Brain }, { path: '/pulse', label: 'Pulse', icon: Activity }, ], }, { label: 'Guardian', items: [ { path: '/guardian/intake', label: 'Forensic Intake', icon: Plus }, { path: '/guardian/journey', label: 'Journey Graph', icon: Activity }, { path: '/guardian/dissonance', label: 'Deception Radar', icon: Mic }, ], }, { label: 'System', items: [ { path: '/cases', label: 'Cases', icon: Layers }, { path: '/simulation', label: 'Simulation', icon: Zap }, { path: '/sentinel', label: 'Sentinel', icon: Shield }, { path: '/safety', label: 'Safety Gateway', icon: Zap }, { path: '/prompts', label: 'Prompts', icon: Terminal }, { path: '/config', label: 'Config', icon: Settings }, ], }, ]; function JanusOrbSmall({ pulse = false }: { pulse?: boolean }) { return (
{pulse && (
)}
); } export default function AppShell({ children }: { children: React.ReactNode }) { const pathname = usePathname(); const router = useRouter(); const [collapsed, setCollapsed] = useState(false); const [mobileOpen, setMobileOpen] = useState(false); const [daemonStatus, setDaemonStatus] = useState(null); const fetchStatus = useCallback(async () => { const baseUrl = getApiBaseUrl(); try { const r = await fetch(`${baseUrl}/daemon/status`); if (r.ok) setDaemonStatus(await r.json()); } catch { /* silent */ } }, []); useEffect(() => { fetchStatus(); const iv = setInterval(fetchStatus, 60000); return () => clearInterval(iv); }, [fetchStatus]); useEffect(() => { setMobileOpen(false); }, [pathname]); const isActive = (path: string) => { if (path === '/') return pathname === '/'; return pathname?.startsWith(path); }; const NavContent = ({ isMobile = false }: { isMobile?: boolean }) => ( <> {/* Logo */}
{(!collapsed || isMobile) && ( JANUS )}
{/* New Chat button */}
{/* Nav sections */} {/* Status footer */} {(!collapsed || isMobile) && (
{daemonStatus?.running ? 'System active' : 'Offline'}
{daemonStatus?.circadian && (
{daemonStatus.circadian.current_phase} phase
)}
)} ); return (
{/* Desktop Sidebar */} {/* Collapse toggle */} {/* Mobile Header */}
JANUS
{/* Mobile Overlay */} {mobileOpen && ( setMobileOpen(false)} /> )} {/* Mobile Drawer */} {mobileOpen && (
)}
{/* Main Content Area */}
{children}
); }