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}
-
+
{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 + + Multica + +
+ {isConnected && ( + + )} +
+ + {/* Main */}
- {!activeAgentId ? ( -
- - Paste a connection code to start + {!isConnected ? ( +
+
+

Paste a connection code to start

+ {(gwState === "connecting" || gwState === "connected") && ( +

Connecting...

+ )} +
+
+