multica/apps/desktop/src/pages/layout.tsx
Naiyuan Qing dacbfa9e3d refactor(ui): purify Chat component and move header to app layouts
- Remove all props from Chat (showHeader, headerActions) making it a
  zero-config pure chat component with only connection input, messages,
  and send functionality
- Create AppHeader client component for web app with brand, theme
  toggle, disconnect button, and useHubInit
- Add disconnect button to desktop layout header
- Add reset() action to hub store to eliminate duplicated state reset
- Remove unused token field from gateway store
- Remove dead code: connection-bar.tsx
- Guard handleConnect against empty deviceId race condition

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 20:31:24 +08:00

90 lines
2.8 KiB
TypeScript

import { Outlet, NavLink, useLocation } from 'react-router-dom'
import { useHubInit, useGatewayStore, useHubStore, clearConnection } from '@multica/store'
import { Toaster } from '@multica/ui/components/ui/sonner'
import { Button } from '@multica/ui/components/ui/button'
import { HugeiconsIcon } from '@hugeicons/react'
import {
Settings02Icon,
Home01Icon,
CodeIcon,
PlugIcon,
Comment01Icon,
} from '@hugeicons/core-free-icons'
import { cn } from '@multica/ui/lib/utils'
const tabs = [
{ path: '/', label: 'Home', icon: Home01Icon, exact: true },
{ path: '/chat', label: 'Chat', icon: Comment01Icon },
{ path: '/tools', label: 'Tools', icon: CodeIcon },
{ path: '/skills', label: 'Skills', icon: PlugIcon },
]
export default function Layout() {
useHubInit()
const location = useLocation()
const gwState = useGatewayStore((s) => s.connectionState)
const hubId = useGatewayStore((s) => s.hubId)
const activeAgentId = useHubStore((s) => s.activeAgentId)
const isConnected = gwState === 'registered' && !!hubId && !!activeAgentId
const handleDisconnect = () => {
useGatewayStore.getState().disconnect()
useHubStore.getState().reset()
clearConnection()
}
return (
<div className="h-dvh flex flex-col bg-background">
{/* Header */}
<header className="flex items-center justify-between px-4 py-3 border-b">
<div className="flex items-center gap-2">
<span className="text-lg font-semibold">Multica</span>
</div>
<div className="flex items-center gap-1">
{isConnected && (
<Button
variant="ghost"
size="sm"
onClick={handleDisconnect}
className="text-xs text-muted-foreground"
>
Disconnect
</Button>
)}
<Button variant="ghost" size="icon">
<HugeiconsIcon icon={Settings02Icon} className="size-5" />
</Button>
</div>
</header>
{/* Tabs */}
<nav className="flex gap-1 px-4 py-2 border-b">
{tabs.map((tab) => {
const isActive = tab.exact
? location.pathname === tab.path
: location.pathname.startsWith(tab.path)
return (
<NavLink key={tab.path} to={tab.path}>
<Button
variant={isActive ? 'secondary' : 'ghost'}
size="sm"
className={cn('gap-2', isActive && 'bg-secondary')}
>
<HugeiconsIcon icon={tab.icon} className="size-4" />
{tab.label}
</Button>
</NavLink>
)
})}
</nav>
{/* Content */}
<main className={cn('flex-1 overflow-auto', location.pathname === '/chat' ? '' : 'p-4')}>
<Outlet />
</main>
<Toaster />
</div>
)
}