Spaces:
Sleeping
Sleeping
| 'use client'; | |
| import { useState } from 'react'; | |
| import Link from 'next/link'; | |
| import { usePathname } from 'next/navigation'; | |
| import { | |
| Brain, | |
| Activity, | |
| ListTodo, | |
| GitBranch, | |
| Database, | |
| Settings, | |
| ChevronLeft, | |
| ChevronRight, | |
| Rocket, | |
| Github, | |
| X, | |
| Menu, | |
| } from 'lucide-react'; | |
| const NAV_ITEMS = [ | |
| { href: '/', icon: Brain, label: 'Agent', desc: 'Run AI tasks' }, | |
| { href: '/health', icon: Activity, label: 'Health', desc: 'System status' }, | |
| { href: '/queue', icon: ListTodo, label: 'Queue', desc: 'Job queue' }, | |
| { href: '/memory', icon: Database, label: 'Memory', desc: 'Episodic memory' }, | |
| { href: '/workspace', icon: GitBranch, label: 'Workspace', desc: 'Projects' }, | |
| ]; | |
| interface AppShellProps { | |
| children: React.ReactNode; | |
| } | |
| export default function AppShell({ children }: AppShellProps) { | |
| const pathname = usePathname(); | |
| const [collapsed, setCollapsed] = useState(false); | |
| const [mobileOpen, setMobileOpen] = useState(false); | |
| return ( | |
| <div className="flex min-h-screen"> | |
| {/* ββ Mobile overlay ββ */} | |
| {mobileOpen && ( | |
| <div | |
| className="fixed inset-0 z-30 bg-black/60 lg:hidden" | |
| onClick={() => setMobileOpen(false)} | |
| /> | |
| )} | |
| {/* ββ Left Sidebar ββ */} | |
| <aside | |
| className={` | |
| fixed top-0 left-0 z-40 h-full flex flex-col | |
| bg-slate-900 border-r border-white/5 | |
| transition-all duration-200 ease-in-out | |
| ${collapsed ? 'w-16' : 'w-56'} | |
| ${mobileOpen ? 'translate-x-0' : '-translate-x-full'} | |
| lg:translate-x-0 lg:static lg:h-auto lg:min-h-screen | |
| `} | |
| > | |
| {/* Logo */} | |
| <div className={`flex items-center gap-2 px-3 py-4 border-b border-white/5 ${collapsed ? 'justify-center' : ''}`}> | |
| <div className="flex items-center justify-center w-8 h-8 rounded-lg bg-brand-600/30 text-brand-400 shrink-0"> | |
| <Rocket className="h-4 w-4" /> | |
| </div> | |
| {!collapsed && ( | |
| <div className="min-w-0"> | |
| <p className="text-sm font-bold truncate">OpenHands</p> | |
| <p className="text-[10px] text-slate-500 truncate">Genspark AI OS</p> | |
| </div> | |
| )} | |
| {/* Collapse toggle β desktop only */} | |
| <button | |
| onClick={() => setCollapsed(v => !v)} | |
| className="ml-auto hidden lg:flex items-center justify-center w-6 h-6 rounded hover:bg-white/5 text-slate-500 hover:text-slate-300" | |
| > | |
| {collapsed ? <ChevronRight className="h-3.5 w-3.5" /> : <ChevronLeft className="h-3.5 w-3.5" />} | |
| </button> | |
| {/* Mobile close */} | |
| <button | |
| onClick={() => setMobileOpen(false)} | |
| className="ml-auto lg:hidden flex items-center justify-center w-6 h-6 rounded hover:bg-white/5 text-slate-500" | |
| > | |
| <X className="h-3.5 w-3.5" /> | |
| </button> | |
| </div> | |
| {/* Phase badge */} | |
| {!collapsed && ( | |
| <div className="mx-3 my-2"> | |
| <span className="inline-flex items-center gap-1 text-[10px] bg-brand-600/20 text-brand-300 border border-brand-600/30 px-2 py-0.5 rounded-full w-full justify-center"> | |
| β Phase 4 Live | |
| </span> | |
| </div> | |
| )} | |
| {/* Nav items */} | |
| <nav className="flex-1 px-2 py-2 space-y-0.5 overflow-y-auto"> | |
| {NAV_ITEMS.map(({ href, icon: Icon, label, desc }) => { | |
| const active = pathname === href || (href !== '/' && pathname.startsWith(href)); | |
| return ( | |
| <Link | |
| key={href} | |
| href={href} | |
| onClick={() => setMobileOpen(false)} | |
| className={` | |
| flex items-center gap-3 rounded-lg px-2 py-2 text-sm transition-colors | |
| ${active | |
| ? 'bg-brand-600/20 text-brand-300 border border-brand-600/20' | |
| : 'text-slate-400 hover:text-slate-200 hover:bg-white/5' | |
| } | |
| ${collapsed ? 'justify-center' : ''} | |
| `} | |
| title={collapsed ? label : undefined} | |
| > | |
| <Icon className={`h-4 w-4 shrink-0 ${active ? 'text-brand-400' : ''}`} /> | |
| {!collapsed && ( | |
| <div className="min-w-0"> | |
| <p className="font-medium leading-none">{label}</p> | |
| <p className="text-[10px] text-slate-500 mt-0.5 truncate">{desc}</p> | |
| </div> | |
| )} | |
| </Link> | |
| ); | |
| })} | |
| </nav> | |
| {/* Bottom β Settings link */} | |
| <div className="border-t border-white/5 px-2 py-2 space-y-0.5"> | |
| <Link | |
| href="/settings" | |
| onClick={() => setMobileOpen(false)} | |
| className={` | |
| flex items-center gap-3 rounded-lg px-2 py-2 text-sm transition-colors | |
| ${pathname === '/settings' | |
| ? 'bg-slate-700/50 text-slate-200' | |
| : 'text-slate-500 hover:text-slate-300 hover:bg-white/5' | |
| } | |
| ${collapsed ? 'justify-center' : ''} | |
| `} | |
| title={collapsed ? 'Settings' : undefined} | |
| > | |
| <Settings className="h-4 w-4 shrink-0" /> | |
| {!collapsed && <span className="font-medium">Settings</span>} | |
| </Link> | |
| {!collapsed && ( | |
| <a | |
| href="https://github.com/pyaesonegtckglay-dotcom/Onehands-development" | |
| target="_blank" | |
| rel="noreferrer" | |
| className="flex items-center gap-3 rounded-lg px-2 py-1.5 text-xs text-slate-600 hover:text-slate-400 hover:bg-white/5 transition-colors" | |
| > | |
| <Github className="h-3.5 w-3.5 shrink-0" /> | |
| <span>GitHub</span> | |
| </a> | |
| )} | |
| </div> | |
| </aside> | |
| {/* ββ Main content area ββ */} | |
| <div className="flex-1 flex flex-col min-w-0"> | |
| {/* Mobile top bar */} | |
| <header className="lg:hidden flex items-center gap-3 px-4 py-3 border-b border-white/5 bg-slate-900/80 backdrop-blur sticky top-0 z-20"> | |
| <button | |
| onClick={() => setMobileOpen(true)} | |
| className="flex items-center justify-center w-8 h-8 rounded-lg hover:bg-white/5 text-slate-400" | |
| > | |
| <Menu className="h-5 w-5" /> | |
| </button> | |
| <div className="flex items-center gap-2"> | |
| <Rocket className="h-4 w-4 text-brand-400" /> | |
| <span className="font-bold text-sm">OpenHands</span> | |
| </div> | |
| <div className="ml-auto"> | |
| <span className="text-[10px] text-brand-400 border border-brand-600/30 bg-brand-600/10 px-2 py-0.5 rounded-full"> | |
| Phase 4 | |
| </span> | |
| </div> | |
| </header> | |
| {/* Page content */} | |
| <main className="flex-1 overflow-auto"> | |
| {children} | |
| </main> | |
| </div> | |
| </div> | |
| ); | |
| } | |