import { create } from 'zustand'; import { persist } from 'zustand/middleware'; interface User { id: string; name: string; email: string; avatar?: string; company?: string; role?: string; } interface ChatMessage { id: string; role: 'user' | 'assistant'; content: string; timestamp: string; sources?: string[]; // 🏆 Competition-winning features suggestions?: string[]; // Dynamic follow-up suggestions confidence?: number; // Data grounding confidence (0-1) mode?: string; // Which AI mode generated this // 📎 Attachment support imageData?: string; // Base64 image data attachments?: Array<{ // File attachments name: string; type: string; size: number; content?: string; // Base64 content for files }>; // 🎯 Direct Answer support (ChatGPT/Claude style) directAnswer?: { value: string; // The direct answer (e.g., "45.2%") type: 'percentage' | 'number' | 'currency' | 'text'; label?: string; // Optional label trend?: 'up' | 'down' | 'stable'; }; } interface Conversation { id: string; title: string; messages: ChatMessage[]; mode: string; createdAt: string; updatedAt: string; } interface UserState { user: User | null; setUser: (user: User) => void; logout: () => void; // Chat history - multiple conversations conversations: Conversation[]; currentConversationId: string | null; // Actions createConversation: (mode?: string) => string; deleteConversation: (id: string) => void; selectConversation: (id: string) => void; addMessageToConversation: (conversationId: string, message: ChatMessage) => void; updateConversationMessages: (conversationId: string, messages: ChatMessage[]) => void; updateConversationTitle: (id: string, title: string) => void; getCurrentConversation: () => Conversation | null; clearCurrentChat: () => void; clearAllChats: () => void; // Stats graphStats: { nodes: number; relationships: number }; queryStats: { totalQueries: number; avgResponseTime: number }; setGraphStats: (stats: { nodes: number; relationships: number }) => void; setQueryStats: (stats: { totalQueries: number; avgResponseTime: number }) => void; // Theme isDark: boolean; toggleTheme: () => void; setTheme: (isDark: boolean) => void; accentTheme: string; setAccentTheme: (theme: string) => void; // Workspaces / Collaboration workspaces: Array<{ id: string; name: string; role: string }>; activeWorkspaceId: string | null; activeWorkspaceRole: string | null; setWorkspaces: (workspaces: Array<{ id: string; name: string; role: string }>) => void; setActiveWorkspace: (id: string | null, role: string | null) => void; } // NOTE: Removed createWelcomeMessage - WelcomeScreen in AnalystChat handles empty state // This allows ChatGPT-style centered input to show on fresh chats const createNewConversation = (mode: string = 'rag'): Conversation => { const now = new Date(); return { id: `conv_${Date.now()}`, title: 'New Chat', messages: [], // Empty - WelcomeScreen will display instead mode, createdAt: now.toISOString(), updatedAt: now.toISOString(), }; }; export const useUserStore = create()( persist( (set, get) => ({ // Start with null - will be populated from AuthContext user: null, setUser: (user) => set({ user }), logout: () => set({ user: null, workspaces: [], activeWorkspaceId: null, activeWorkspaceRole: null }), workspaces: [], activeWorkspaceId: null, activeWorkspaceRole: null, setWorkspaces: (workspaces) => set({ workspaces }), setActiveWorkspace: (id, role) => set({ activeWorkspaceId: id, activeWorkspaceRole: role }), // Initialize with one conversation conversations: [createNewConversation()], currentConversationId: null, // Create new conversation createConversation: (mode = 'rag') => { const newConv = createNewConversation(mode); set((state) => ({ conversations: [newConv, ...state.conversations], currentConversationId: newConv.id, })); return newConv.id; }, // Delete conversation deleteConversation: (id) => { set((state) => { const filtered = state.conversations.filter(c => c.id !== id); let newCurrentId = state.currentConversationId; if (state.currentConversationId === id) { if (filtered.length > 0) { newCurrentId = filtered[0].id; } else { const newConv = createNewConversation(); filtered.push(newConv); newCurrentId = newConv.id; } } return { conversations: filtered, currentConversationId: newCurrentId, }; }); }, // Select conversation selectConversation: (id) => { set({ currentConversationId: id }); }, // Add message to conversation addMessageToConversation: (conversationId, message) => { set((state) => ({ conversations: state.conversations.map(conv => { if (conv.id === conversationId) { let title = conv.title; if (title === 'New Chat' && message.role === 'user') { title = message.content.substring(0, 35) + (message.content.length > 35 ? '...' : ''); } return { ...conv, title, messages: [...conv.messages, message], updatedAt: new Date().toISOString(), }; } return conv; }), })); }, // Update conversation messages (for edit/resend feature) updateConversationMessages: (conversationId, messages) => { set((state) => ({ conversations: state.conversations.map(conv => { if (conv.id === conversationId) { return { ...conv, messages, updatedAt: new Date().toISOString(), }; } return conv; }), })); }, // Update conversation title updateConversationTitle: (id, title) => { set((state) => ({ conversations: state.conversations.map(conv => conv.id === id ? { ...conv, title } : conv ), })); }, // Get current conversation getCurrentConversation: () => { const state = get(); if (!state.currentConversationId) { if (state.conversations.length > 0) { return state.conversations[0]; } return null; } return state.conversations.find(c => c.id === state.currentConversationId) || null; }, // Clear current chat messages clearCurrentChat: () => { set((state) => ({ conversations: state.conversations.map(conv => { if (conv.id === state.currentConversationId) { return { ...conv, messages: [], // Empty - shows WelcomeScreen title: 'New Chat', updatedAt: new Date().toISOString(), }; } return conv; }), })); }, // Clear all chats and start fresh clearAllChats: () => { const newConv = createNewConversation(); set({ conversations: [newConv], currentConversationId: newConv.id, }); }, // Stats graphStats: { nodes: 0, relationships: 0 }, queryStats: { totalQueries: 0, avgResponseTime: 0 }, setGraphStats: (stats) => set({ graphStats: stats }), setQueryStats: (stats) => set({ queryStats: stats }), // Theme logic - Initialize from userPreferences if available isDark: (() => { try { // Check userPreferences first (Settings page source of truth) const savedPrefs = localStorage.getItem('userPreferences'); if (savedPrefs) { const parsed = JSON.parse(savedPrefs); if (parsed.preferences?.theme === 'light') return false; if (parsed.preferences?.theme === 'dark') return true; } // Fallback to persisted store or default return true; } catch (e) { return true; } })(), toggleTheme: () => set((state) => { const newIsDark = !state.isDark; // Update DOM immediately if (newIsDark) { document.documentElement.classList.remove('light-theme'); document.documentElement.classList.add('dark'); } else { document.documentElement.classList.add('light-theme'); document.documentElement.classList.remove('dark'); } // Sync with userPreferences for persistence try { const userId = get().user?.id || localStorage.getItem('userId') || 'default'; // Update global pref const saved = localStorage.getItem('userPreferences'); let prefs = saved ? JSON.parse(saved) : { preferences: {} }; if (!prefs.preferences) prefs.preferences = {}; prefs.preferences.theme = newIsDark ? 'dark' : 'light'; localStorage.setItem('userPreferences', JSON.stringify(prefs)); // Update user specific pref if possible if (userId && userId !== 'default') { localStorage.setItem(`userPreferences_${userId}`, JSON.stringify(prefs)); } } catch (e) { console.error("Failed to sync theme preference", e); } return { isDark: newIsDark }; }), setTheme: (isDark) => set(() => { if (isDark) { document.documentElement.classList.remove('light-theme'); document.documentElement.classList.add('dark'); } else { document.documentElement.classList.add('light-theme'); document.documentElement.classList.remove('dark'); } return { isDark }; }), accentTheme: (() => { try { const savedPrefs = localStorage.getItem('userPreferences'); if (savedPrefs) { const parsed = JSON.parse(savedPrefs); if (parsed.preferences?.accentTheme) return parsed.preferences.accentTheme; } return 'default'; } catch (e) { return 'default'; } })(), setAccentTheme: (theme) => set((state) => { try { const userId = get().user?.id || localStorage.getItem('userId') || 'default'; const saved = localStorage.getItem('userPreferences'); let prefs = saved ? JSON.parse(saved) : { preferences: {} }; if (!prefs.preferences) prefs.preferences = {}; prefs.preferences.accentTheme = theme; localStorage.setItem('userPreferences', JSON.stringify(prefs)); if (userId && userId !== 'default') { localStorage.setItem(`userPreferences_${userId}`, JSON.stringify(prefs)); } } catch (e) { console.error("Failed to sync accent theme", e); } return { accentTheme: theme }; }), }), { name: 'ai-analyst-storage-v2', partialize: (state) => ({ user: state.user, // Persist user data including avatar conversations: state.conversations, currentConversationId: state.currentConversationId, graphStats: state.graphStats, queryStats: state.queryStats, isDark: state.isDark, // Persist theme accentTheme: state.accentTheme, // Persist accent theme }), } ) );