diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index 9d299154..4c9091a3 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -3,7 +3,6 @@ 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 { Toaster } from "@multica/ui/components/ui/sonner";
-import { ConnectionBar } from "@multica/ui/components/connection-bar";
import { ServiceWorkerRegister } from "./sw-register";
const inter = Inter({ subsets: ["latin"], variable: "--font-sans" });
@@ -53,10 +52,7 @@ export default function RootLayout({
enableSystem
disableTransitionOnChange
>
-
+ {children}
diff --git a/packages/ui/src/components/chat.tsx b/packages/ui/src/components/chat.tsx
index 2a4f88b1..3a90d661 100644
--- a/packages/ui/src/components/chat.tsx
+++ b/packages/ui/src/components/chat.tsx
@@ -1,24 +1,60 @@
"use client";
-import { useRef, useCallback, useMemo } from "react";
+import { useRef, useState, useCallback, useMemo } from "react";
+import { Button } from "@multica/ui/components/ui/button";
+import { Textarea } from "@multica/ui/components/ui/textarea";
import { ChatInput } from "@multica/ui/components/chat-input";
import { MemoizedMarkdown } from "@multica/ui/components/markdown";
import { StreamingMarkdown } from "@multica/ui/components/markdown/StreamingMarkdown";
-import { HugeiconsIcon } from "@hugeicons/react";
-import { UserIcon } from "@hugeicons/core-free-icons";
-import { useHubStore, useMessagesStore, useGatewayStore } from "@multica/store";
+import { toast } from "@multica/ui/components/ui/sonner";
+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";
export function Chat() {
+ useHubInit()
+ const deviceId = useDeviceId()
+
const activeAgentId = useHubStore((s) => s.activeAgentId)
const gwState = useGatewayStore((s) => s.connectionState)
+ const hubId = useGatewayStore((s) => s.hubId)
const messages = useMessagesStore((s) => s.messages)
const streamingIds = useMessagesStore((s) => s.streamingIds)
const filtered = useMemo(() => messages.filter(m => m.agentId === activeAgentId), [messages, activeAgentId])
+ const isConnected = gwState === "registered" && !!hubId && !!activeAgentId
+ const [codeInput, setCodeInput] = useState("")
+
+ const handleConnect = useCallback(() => {
+ 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)
+ }
+ }, [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
@@ -27,19 +63,65 @@ export function Chat() {
useGatewayStore.getState().send(hubId, "message", { agentId, content: text })
}, [])
- const canSend = gwState === "registered" && !!activeAgentId
-
const mainRef = useRef(null)
const fadeStyle = useScrollFade(mainRef)
useAutoScroll(mainRef)
return (
+ {/* Header */}
+
+
+

+
+ Multica
+
+
+ {isConnected && (
+
+ )}
+
+
+ {/* Main */}
- {!activeAgentId ? (
-
-
-
Paste a connection code to start
+ {!isConnected ? (
+
+
+
Paste a connection code to start
+ {(gwState === "connecting" || gwState === "connected") && (
+
Connecting...
+ )}
+
+
+
) : filtered.length === 0 ? (
@@ -77,11 +159,12 @@ export function Chat() {
)}
+ {/* Footer */}