diff --git a/packages/ui/src/components/chat-skeleton.tsx b/packages/ui/src/components/chat-skeleton.tsx new file mode 100644 index 00000000..405d2723 --- /dev/null +++ b/packages/ui/src/components/chat-skeleton.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { Skeleton } from "@multica/ui/components/ui/skeleton"; + +/** Skeleton placeholder matching MessageList layout, shown while reconnecting */ +export function ChatSkeleton() { + return ( +
+ {/* Assistant message */} +
+
+ + +
+
+ + {/* User message */} +
+
+ +
+
+ + {/* Assistant message */} +
+
+ + + +
+
+ + {/* User message */} +
+
+ +
+
+
+ ); +} diff --git a/packages/ui/src/components/chat.tsx b/packages/ui/src/components/chat.tsx index 5848f510..fa3d7773 100644 --- a/packages/ui/src/components/chat.tsx +++ b/packages/ui/src/components/chat.tsx @@ -1,56 +1,35 @@ "use client"; -import { useRef, useState, useCallback, useMemo } from "react"; +import { useRef, useCallback } 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 { toast } from "@multica/ui/components/ui/sonner"; -import { - useHubStore, - useMessagesStore, - useGatewayStore, - useDeviceId, - parseConnectionCode, - saveConnection, -} from "@multica/store"; +import { useConnectionStore, useMessagesStore, useAutoConnect } 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"; +import { ConnectPrompt } from "./connect-prompt"; +import { MessageList } from "./message-list"; +import { ChatSkeleton } from "./chat-skeleton"; export function Chat() { - const deviceId = useDeviceId() - const activeAgentId = useHubStore((s) => s.activeAgentId) - const gwState = useGatewayStore((s) => s.connectionState) - const hubId = useGatewayStore((s) => s.hubId) + const { loading } = useAutoConnect() + + const agentId = useConnectionStore((s) => s.agentId) + const gwState = useConnectionStore((s) => s.connectionState) + const hubId = useConnectionStore((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 || !deviceId) 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 isConnected = gwState === "registered" && !!hubId && !!agentId const handleSend = useCallback((text: string) => { - const { hubId } = useGatewayStore.getState() - const agentId = useHubStore.getState().activeAgentId - if (!hubId || !agentId) return - useMessagesStore.getState().addUserMessage(text, agentId) - useGatewayStore.getState().send(hubId, "message", { agentId, content: text }) + const { hubId, agentId, send, connectionState } = useConnectionStore.getState() + if (connectionState !== "registered" || !hubId || !agentId) return + useMessagesStore.getState().sendMessage(text, { hubId, agentId, send }) + }, []) + + const handleDisconnect = useCallback(() => { + useConnectionStore.getState().disconnect() }, []) const mainRef = useRef(null) @@ -59,71 +38,30 @@ export function Chat() { return (
+ {isConnected && ( +
+ +
+ )} +
- {!isConnected ? ( -
-
-

Paste a connection code to start

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

Connecting...

- )} -
-
-