import { create } from "zustand"; import { persist } from "zustand/middleware"; import type { ChatState, Conversation } from "./types"; function genId(): string { return crypto.randomUUID(); } export const useChatStore = create()( persist( (set, get) => ({ messages: [], isStreaming: false, error: null, conversations: [], activeConversationId: null, personId: crypto.randomUUID(), // --- Messages --- addUserMessage: (content: string) => { const id = genId(); const now = Date.now(); set((s) => { const updated = { messages: [ ...s.messages, { id, role: "user" as const, content, timestamp: now }, ], error: null, }; // Update conversation title from first user message if (s.activeConversationId) { const conv = s.conversations.find( (c) => c.id === s.activeConversationId ); if (conv && conv.title === "New conversation") { return { ...updated, conversations: s.conversations.map((c) => c.id === s.activeConversationId ? { ...c, title: content.slice(0, 60), lastMessageAt: now, } : c ), }; } return { ...updated, conversations: s.conversations.map((c) => c.id === s.activeConversationId ? { ...c, lastMessageAt: now } : c ), }; } return updated; }); return id; }, startStreaming: () => { const id = genId(); set((s) => ({ messages: [ ...s.messages, { id, role: "assistant" as const, content: "", timestamp: Date.now(), }, ], isStreaming: true, error: null, })); return id; }, appendToken: (id: string, token: string) => { set((s) => ({ messages: s.messages.map((m) => m.id === id ? { ...m, content: m.content + token } : m ), })); }, finishStreaming: (id: string) => { set((s) => ({ messages: s.messages.map((m) => m.id === id ? { ...m, timestamp: Date.now() } : m ), isStreaming: false, })); }, setError: (error: string | null) => { set({ error, isStreaming: false }); }, clearMessages: () => { set({ messages: [], error: null, isStreaming: false }); }, // --- Conversations --- createConversation: () => { const id = genId(); const now = Date.now(); const conv: Conversation = { id, title: "New conversation", createdAt: now, lastMessageAt: now, }; set((s) => ({ conversations: [conv, ...s.conversations], activeConversationId: id, messages: [], error: null, isStreaming: false, })); return id; }, switchConversation: (id: string) => { const { activeConversationId } = get(); if (id === activeConversationId) return; set({ activeConversationId: id, messages: [], error: null, isStreaming: false, }); }, setMessages: (msgs) => { set({ messages: msgs }); }, updateConversationTitle: (id: string, title: string) => { set((s) => ({ conversations: s.conversations.map((c) => c.id === id ? { ...c, title: title.slice(0, 60) } : c ), })); }, deleteConversation: (id: string) => { set((s) => { const filtered = s.conversations.filter((c) => c.id !== id); const wasActive = s.activeConversationId === id; return { conversations: filtered, activeConversationId: wasActive ? filtered[0]?.id ?? null : s.activeConversationId, messages: wasActive ? [] : s.messages, }; }); }, switchPerson: () => { const newId = genId(); set({ personId: newId, conversations: [], activeConversationId: null, messages: [], error: null, isStreaming: false, }); return newId; }, }), { name: "aeries-conversations", partialize: (state) => ({ conversations: state.conversations, activeConversationId: state.activeConversationId, personId: state.personId, }), } ) );