diff --git a/apps/desktop/src/pages/chat.tsx b/apps/desktop/src/pages/chat.tsx
index a4d6e0a5..52d406ed 100644
--- a/apps/desktop/src/pages/chat.tsx
+++ b/apps/desktop/src/pages/chat.tsx
@@ -1,5 +1,5 @@
import { Chat } from '@multica/ui/components/chat'
export default function ChatPage() {
- return
+ return
}
diff --git a/apps/desktop/src/pages/layout.tsx b/apps/desktop/src/pages/layout.tsx
index 672ca01e..48d167ff 100644
--- a/apps/desktop/src/pages/layout.tsx
+++ b/apps/desktop/src/pages/layout.tsx
@@ -1,4 +1,5 @@
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'
@@ -19,8 +20,20 @@ const tabs = [
]
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 (
{/* Header */}
@@ -28,9 +41,21 @@ export default function Layout() {
Multica
-
+
+ {isConnected && (
+
+ )}
+
+
{/* Tabs */}
diff --git a/apps/web/app/app-header.tsx b/apps/web/app/app-header.tsx
new file mode 100644
index 00000000..e6052a26
--- /dev/null
+++ b/apps/web/app/app-header.tsx
@@ -0,0 +1,54 @@
+"use client";
+
+import {
+ useHubInit,
+ useGatewayStore,
+ useHubStore,
+ clearConnection,
+} from "@multica/store";
+import { Button } from "@multica/ui/components/ui/button";
+import { ThemeToggle } from "./theme-toggle";
+
+export function AppHeader({ children }: { children: React.ReactNode }) {
+ useHubInit();
+
+ 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 (
+ <>
+
+ {children}
+ >
+ );
+}
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index f6c38402..e83481a0 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -2,7 +2,7 @@ import type { Metadata } from "next";
import { Geist, Geist_Mono, Inter, Playfair_Display } from "next/font/google";
import "@multica/ui/globals.css";
import { ThemeProvider } from "@multica/ui/components/theme-provider";
-import { ThemeToggle } from "./theme-toggle";
+import { AppHeader } from "./app-header";
import { Toaster } from "@multica/ui/components/ui/sonner";
import { ServiceWorkerRegister } from "./sw-register";
@@ -45,7 +45,7 @@ export default function RootLayout({
return (
-
- {children}
+
+ {children}
+
diff --git a/apps/web/app/theme-toggle.tsx b/apps/web/app/theme-toggle.tsx
index 60342b4b..70eb691e 100644
--- a/apps/web/app/theme-toggle.tsx
+++ b/apps/web/app/theme-toggle.tsx
@@ -9,16 +9,14 @@ export function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
-
-
-
+
);
}
diff --git a/packages/store/src/gateway.ts b/packages/store/src/gateway.ts
index 8a4ec2cd..009848d4 100644
--- a/packages/store/src/gateway.ts
+++ b/packages/store/src/gateway.ts
@@ -10,7 +10,6 @@ interface GatewayState {
connectionState: ConnectionState
hubId: string | null
agentId: string | null
- token: string | null
hubs: DeviceInfo[]
lastError: SendErrorResponse | null
}
@@ -80,7 +79,6 @@ export const useGatewayStore = create
()((set, get) => ({
connectionState: "disconnected",
hubId: null,
agentId: null,
- token: null,
hubs: [],
lastError: null,
@@ -103,7 +101,6 @@ export const useGatewayStore = create()((set, get) => ({
gatewayUrl: code.gateway,
hubId: code.hubId,
agentId: code.agentId,
- token: code.token,
})
client = createClient(code.gateway, deviceId, set)
@@ -115,7 +112,7 @@ export const useGatewayStore = create()((set, get) => ({
client.disconnect()
client = null
}
- set({ connectionState: "disconnected", hubId: null, agentId: null, token: null, hubs: [] })
+ set({ connectionState: "disconnected", hubId: null, agentId: null, hubs: [] })
},
setHubId: (hubId) => set({ hubId }),
diff --git a/packages/store/src/hub-init.ts b/packages/store/src/hub-init.ts
index a3843bdf..d9735981 100644
--- a/packages/store/src/hub-init.ts
+++ b/packages/store/src/hub-init.ts
@@ -11,6 +11,7 @@ export function useHubInit() {
const gwState = useGatewayStore((s) => s.connectionState)
const hubId = useGatewayStore((s) => s.hubId)
const agentId = useGatewayStore((s) => s.agentId)
+ const reset = useHubStore((s) => s.reset)
const fetchHub = useHubStore((s) => s.fetchHub)
const fetchAgents = useHubStore((s) => s.fetchAgents)
const setActiveAgentId = useHubStore((s) => s.setActiveAgentId)
@@ -35,7 +36,7 @@ export function useHubInit() {
}
}
if (gwState === "disconnected") {
- useHubStore.setState({ status: "idle", hub: null, agents: [], activeAgentId: null })
+ reset()
}
- }, [gwState, hubId, agentId, fetchHub, fetchAgents, setActiveAgentId])
+ }, [gwState, hubId, agentId, reset, fetchHub, fetchAgents, setActiveAgentId])
}
diff --git a/packages/store/src/hub.ts b/packages/store/src/hub.ts
index 2bb0ef70..f06ca1fe 100644
--- a/packages/store/src/hub.ts
+++ b/packages/store/src/hub.ts
@@ -38,6 +38,7 @@ interface HubState {
}
interface HubActions {
+ reset: () => void
setActiveAgentId: (id: string | null) => void
fetchHub: () => Promise
fetchAgents: () => Promise
@@ -54,6 +55,8 @@ export const useHubStore = create()((set, get) => ({
agents: [],
activeAgentId: null,
+ reset: () => set({ status: "idle", hub: null, agents: [], activeAgentId: null }),
+
setActiveAgentId: (id) => {
set({ activeAgentId: id })
if (id) {
diff --git a/packages/ui/src/components/chat.tsx b/packages/ui/src/components/chat.tsx
index 575ccb8e..5848f510 100644
--- a/packages/ui/src/components/chat.tsx
+++ b/packages/ui/src/components/chat.tsx
@@ -11,22 +11,15 @@ import {
useHubStore,
useMessagesStore,
useGatewayStore,
- useHubInit,
useDeviceId,
parseConnectionCode,
saveConnection,
- clearConnection,
} from "@multica/store";
import { useScrollFade } from "@multica/ui/hooks/use-scroll-fade";
import { useAutoScroll } from "@multica/ui/hooks/use-auto-scroll";
import { cn } from "@multica/ui/lib/utils";
-interface ChatProps {
- showHeader?: boolean;
-}
-
-export function Chat({ showHeader = true }: ChatProps) {
- useHubInit()
+export function Chat() {
const deviceId = useDeviceId()
const activeAgentId = useHubStore((s) => s.activeAgentId)
const gwState = useGatewayStore((s) => s.connectionState)
@@ -41,7 +34,7 @@ export function Chat({ showHeader = true }: ChatProps) {
const handleConnect = useCallback(() => {
const trimmed = codeInput.trim()
- if (!trimmed) return
+ if (!trimmed || !deviceId) return
try {
const info = parseConnectionCode(trimmed)
saveConnection(info)
@@ -52,12 +45,6 @@ export function Chat({ showHeader = true }: ChatProps) {
}
}, [codeInput, deviceId])
- const handleDisconnect = useCallback(() => {
- useGatewayStore.getState().disconnect()
- useHubStore.setState({ status: "idle", hub: null, agents: [], activeAgentId: null })
- clearConnection()
- }, [])
-
const handleSend = useCallback((text: string) => {
const { hubId } = useGatewayStore.getState()
const agentId = useHubStore.getState().activeAgentId
@@ -72,33 +59,6 @@ export function Chat({ showHeader = true }: ChatProps) {
return (
- {/* Header */}
- {showHeader && (
-
- )}
-
- {/* Main */}
{!isConnected ? (
diff --git a/packages/ui/src/components/connection-bar.tsx b/packages/ui/src/components/connection-bar.tsx
deleted file mode 100644
index bbc356df..00000000
--- a/packages/ui/src/components/connection-bar.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-"use client"
-
-import { useState } from "react"
-import { Button } from "@multica/ui/components/ui/button"
-import { Textarea } from "@multica/ui/components/ui/textarea"
-import { toast } from "@multica/ui/components/ui/sonner"
-import {
- useGatewayStore,
- useHubStore,
- useDeviceId,
- useHubInit,
- parseConnectionCode,
- saveConnection,
- clearConnection,
-} from "@multica/store"
-
-const STATUS_DOT: Record
= {
- registered: "bg-green-500",
- connected: "bg-yellow-500 animate-pulse",
- connecting: "bg-yellow-500 animate-pulse",
- disconnected: "bg-red-500",
-}
-
-export function ConnectionBar() {
- useHubInit()
-
- const deviceId = useDeviceId()
- const gwState = useGatewayStore((s) => s.connectionState)
- const hubId = useGatewayStore((s) => s.hubId)
- const agentId = useGatewayStore((s) => s.agentId)
- const hubStatus = useHubStore((s) => s.status)
-
- const isConnected = gwState === "registered" && hubId
- const [codeInput, setCodeInput] = useState("")
-
- const handleConnect = () => {
- const trimmed = codeInput.trim()
- if (!trimmed) return
- try {
- const info = parseConnectionCode(trimmed)
- saveConnection(info)
- useGatewayStore.getState().connectWithCode(info, deviceId)
- setCodeInput("")
- } catch (e) {
- toast.error((e as Error).message)
- }
- }
-
- const handleDisconnect = () => {
- useGatewayStore.getState().disconnect()
- useHubStore.setState({ status: "idle", hub: null, agents: [], activeAgentId: null })
- clearConnection()
- }
-
- return (
-
-
-

-
- Multica
-
-
-
-
- {isConnected ? (
-
-
-
-
- {hubStatus === "connected" ? "Connected" : "Connecting..."}
-
-
-
-
Hub: {hubId}
- {agentId &&
Agent: {agentId}
}
-
-
-
- ) : gwState === "connecting" || gwState === "connected" ? (
-
-
- Connecting...
-
- ) : (
-
-
- )}
-
-
- )
-}