From 97fce5b1131a68a4c598cd5a3d7d2e6b1941f9e4 Mon Sep 17 00:00:00 2001 From: Naiyuan Qing <145280634+NevilleQingNY@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:49:52 +0800 Subject: [PATCH] refactor(store): migrate messages hook to Zustand store - Move useMessages from apps/web to packages/store/src/messages.ts - Convert useState to Zustand store for global message persistence - Add reserved interfaces: updateMessage, loadMessages, getMessagesByAgent - Update chat.tsx imports to use @multica/store Co-Authored-By: Claude Opus 4.5 --- apps/web/app/components/chat.tsx | 8 ++-- apps/web/app/hooks/use-messages.ts | 25 ------------ packages/store/src/index.ts | 2 + packages/store/src/messages.ts | 62 ++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 29 deletions(-) delete mode 100644 apps/web/app/hooks/use-messages.ts create mode 100644 packages/store/src/messages.ts diff --git a/apps/web/app/components/chat.tsx b/apps/web/app/components/chat.tsx index e819c7e8..ee114746 100644 --- a/apps/web/app/components/chat.tsx +++ b/apps/web/app/components/chat.tsx @@ -9,10 +9,8 @@ import { MemoizedMarkdown } from "@multica/ui/components/markdown"; import { HugeiconsIcon } from "@hugeicons/react"; import { UserIcon, Copy01Icon, CheckmarkCircle02Icon } from "@hugeicons/core-free-icons"; import { toast } from "@multica/ui/components/ui/sonner"; -import { useMessages } from "../hooks/use-messages"; import { useGateway } from "../hooks/use-gateway"; -import { useHubStore } from "@multica/store"; -import { useDeviceId } from "@multica/store"; +import { useHubStore, useDeviceId, useMessagesStore } from "@multica/store"; import { useScrollFade } from "../hooks/use-scroll-fade"; import { cn } from "@multica/ui/lib/utils"; @@ -26,7 +24,9 @@ const STATE_VARIANT: Record s.activeAgentId) const hub = useHubStore((s) => s.hub) - const { messages, addUserMessage, addAssistantMessage } = useMessages() + const addUserMessage = useMessagesStore((s) => s.addUserMessage) + const addAssistantMessage = useMessagesStore((s) => s.addAssistantMessage) + const messages = useMessagesStore((s) => s.messages) const { state: gwState, send } = useGateway({ onMessage: (msg) => { diff --git a/apps/web/app/hooks/use-messages.ts b/apps/web/app/hooks/use-messages.ts deleted file mode 100644 index 8982b0d4..00000000 --- a/apps/web/app/hooks/use-messages.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useState, useCallback } from "react" -import { v7 as uuidv7 } from "uuid" - -export interface Message { - id: string - role: "user" | "assistant" - content: string - agentId: string -} - -export function useMessages() { - const [messages, setMessages] = useState([]) - - const addUserMessage = useCallback((content: string, agentId: string) => { - setMessages(prev => [...prev, { id: uuidv7(), role: "user", content, agentId }]) - }, []) - - const addAssistantMessage = useCallback((content: string, agentId: string) => { - setMessages(prev => [...prev, { id: uuidv7(), role: "assistant", content, agentId }]) - }, []) - - const clearMessages = useCallback(() => setMessages([]), []) - - return { messages, addUserMessage, addAssistantMessage, clearMessages } -} diff --git a/packages/store/src/index.ts b/packages/store/src/index.ts index 0f6fe37b..32002eba 100644 --- a/packages/store/src/index.ts +++ b/packages/store/src/index.ts @@ -2,3 +2,5 @@ export { useHubStore } from "./hub" export type { HubInfo, Agent, HubStatus, HubStore } from "./hub" export { useHubInit } from "./hub-init" export { useDeviceId } from "./device-id" +export { useMessagesStore } from "./messages" +export type { Message, MessagesStore } from "./messages" diff --git a/packages/store/src/messages.ts b/packages/store/src/messages.ts new file mode 100644 index 00000000..1c9d7bbb --- /dev/null +++ b/packages/store/src/messages.ts @@ -0,0 +1,62 @@ +import { create } from "zustand" +import { v7 as uuidv7 } from "uuid" + +export interface Message { + id: string + role: "user" | "assistant" + content: string + agentId: string +} + +interface MessagesState { + messages: Message[] +} + +interface MessagesActions { + addUserMessage: (content: string, agentId: string) => void + addAssistantMessage: (content: string, agentId: string) => void + updateMessage: (id: string, content: string) => void + loadMessages: (agentId: string, msgs: Message[]) => void + getMessagesByAgent: (agentId: string) => Message[] + clearMessages: (agentId?: string) => void +} + +export type MessagesStore = MessagesState & MessagesActions + +export const useMessagesStore = create()((set, get) => ({ + messages: [], + + addUserMessage: (content, agentId) => { + set((s) => ({ + messages: [...s.messages, { id: uuidv7(), role: "user", content, agentId }], + })) + }, + + addAssistantMessage: (content, agentId) => { + set((s) => ({ + messages: [...s.messages, { id: uuidv7(), role: "assistant", content, agentId }], + })) + }, + + updateMessage: (id, content) => { + set((s) => ({ + messages: s.messages.map((m) => (m.id === id ? { ...m, content } : m)), + })) + }, + + loadMessages: (agentId, msgs) => { + set((s) => ({ + messages: [...s.messages.filter((m) => m.agentId !== agentId), ...msgs], + })) + }, + + getMessagesByAgent: (agentId) => { + return get().messages.filter((m) => m.agentId === agentId) + }, + + clearMessages: (agentId?) => { + set((s) => ({ + messages: agentId ? s.messages.filter((m) => m.agentId !== agentId) : [], + })) + }, +}))