import React, { useState, useEffect } from 'react'; import { MessageSquare, SquarePen, Search, MoreVertical, Trash2, LogOut, User, UserCircle, DatabaseZap, KeyRound, PanelLeft, FileText, ChevronRight, Clock } from 'lucide-react'; import * as LucideIcons from 'lucide-react'; import { useAppConfig } from '../contexts/AppConfigContext'; import UserAvatarPicker from './UserAvatarPicker'; import CopyrightNotice from './CopyrightNotice'; import '../styles/Sidebar.css'; const Sidebar = ({ user, currentSessionId, onSelectSession, onNewChat, onSignOut, authToken, onSidebarToggle, isMobileOpen = false, onMobileToggle, refreshTrigger, onCurrentSessionDeleted, pageContext = 'chat', canvasItems = [], canvasSubview = 'workspace', widgetGroups = [], deliverableProjects = [], insightSections = [], userAvatarId, onAvatarChange, onOpenProfile, onOpenAccount, onOpenClearData, }) => { const { config } = useAppConfig(); const isOnCanvas = pageContext === 'canvas'; const [showAvatarPicker, setShowAvatarPicker] = useState(false); const avatarOptions = config?.app?.user_avatars || []; const currentAvatar = avatarOptions.find(a => a.id === userAvatarId); const AvatarIcon = currentAvatar ? (LucideIcons[currentAvatar.icon] || User) : User; const [expanded, setExpanded] = useState(() => { try { return JSON.parse(localStorage.getItem('sidebar-expanded-v1') || '{}'); } catch { return {}; } }); const toggleExpanded = (key) => { setExpanded(prev => { const next = { ...prev, [key]: !prev[key] }; localStorage.setItem('sidebar-expanded-v1', JSON.stringify(next)); return next; }); }; const [chatSessions, setChatSessions] = useState([]); const [searchTerm, setSearchTerm] = useState(''); const [isLoading, setIsLoading] = useState(true); const [showUserMenu, setShowUserMenu] = useState(false); const [isCollapsed, setIsCollapsed] = useState(false); const [isCreatingNewChat, setIsCreatingNewChat] = useState(false); useEffect(() => { if (authToken) { fetchChatSessions(); } }, [authToken]); useEffect(() => { const handleOverlayClick = (e) => { // Only close if clicking the overlay itself, not the sidebar if (e.target.classList.contains('mobile-sidebar-overlay')) { onMobileToggle(false); } }; if (isMobileOpen) { document.addEventListener('click', handleOverlayClick); return () => document.removeEventListener('click', handleOverlayClick); } }, [isMobileOpen, onMobileToggle]); // Notify parent when sidebar state changes useEffect(() => { if (onSidebarToggle) { onSidebarToggle(isCollapsed); } }, [isCollapsed, onSidebarToggle]); // Add effect to refresh when currentSessionId changes (new session created) useEffect(() => { if (currentSessionId && authToken) { // Small delay to ensure the session is saved to database const timer = setTimeout(() => { fetchChatSessions(); }, 200); return () => clearTimeout(timer); } }, [currentSessionId, authToken]); // Refresh session list when parent signals a message exchange completed useEffect(() => { if (refreshTrigger > 0 && authToken) { fetchChatSessions(); } }, [refreshTrigger]); const fetchChatSessions = async () => { try { const response = await fetch(`${process.env.REACT_APP_API_URL}/api/chat-sessions`, { headers: { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/json' } }); if (response.ok) { const sessions = await response.json(); setChatSessions(sessions); } else { console.error('Failed to fetch chat sessions'); } } catch (error) { console.error('Error fetching chat sessions:', error); } finally { setIsLoading(false); } }; const handleNewChat = async () => { setIsCreatingNewChat(true); try { // Call the parent's new chat handler and wait for it to complete await onNewChat(); // Refresh the sessions list immediately after new chat is created // The parent should have updated currentSessionId by now await fetchChatSessions(); } catch (error) { console.error('Error creating new chat:', error); // Optionally show an error message to the user } finally { setIsCreatingNewChat(false); } }; const handleDeleteSession = async (sessionId, event) => { event.stopPropagation(); if (window.confirm('Are you sure you want to delete this chat?')) { try { const response = await fetch(`${process.env.REACT_APP_API_URL}/api/chat-sessions/${sessionId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/json' } }); if (response.ok) { setChatSessions(prev => prev.filter(session => session.id !== sessionId)); if (currentSessionId === sessionId) { onCurrentSessionDeleted?.(); } } } catch (error) { console.error('Error deleting chat session:', error); } } }; const toggleSidebar = () => { setIsCollapsed(!isCollapsed); // Close user menu when collapsing if (!isCollapsed) { setShowUserMenu(false); } }; const filteredSessions = chatSessions.filter(session => session.title.toLowerCase().includes(searchTerm.toLowerCase()) ); const formatDate = (dateString) => { const date = new Date(dateString); const now = new Date(); const diffTime = Math.abs(now - date); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); if (diffDays === 1) return 'Today'; if (diffDays === 2) return 'Yesterday'; if (diffDays <= 7) return `${diffDays - 1} days ago`; return date.toLocaleDateString(); }; return ( <>
{!isCollapsed && Neon.ai}
{!isCollapsed &&