'use client'; import { useState, useEffect } from 'react'; import { cn } from '@/lib/utils'; import { ConversationHistorySidebar } from '@/components/chat/conversation-history-sidebar'; import { MessageCircle, X, Bot, Brain, Globe, Database, Menu, LogOut, ChevronLeft, ChevronRight, History, Search, MoreHorizontal, Clock, Filter, BarChart3, Users } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; import { VersionDisplay } from '@/components/ui/version-display'; import Link from 'next/link'; import Image from 'next/image'; import { usePathname } from 'next/navigation'; import { User } from '@/types'; import { useAuthStore } from '@/stores/auth-store'; import { useChatStore } from '@/stores/chat-store'; import { getInitials } from '@/lib/utils'; import { getAuthToken, getTenantInfo } from '@/services/auth'; import { getUserRole } from '@/lib/permissions'; interface SidebarProps { user: User | null; onCollapseChange?: (collapsed: boolean) => void; onSelectConversation?: (conversationId: string) => void; } export function Sidebar({ user, onCollapseChange, onSelectConversation }: SidebarProps) { const pathname = usePathname(); const { logout } = useAuthStore(); const { unreadCounts } = useChatStore(); const [showUserMenu, setShowUserMenu] = useState(false); const [tenantInfo, setTenantInfo] = useState<{name: string; domain: string} | null>(null); const [availableAgents, setAvailableAgents] = useState<{id: string, name: string}[]>([]); const [isCollapsed, setIsCollapsed] = useState(() => { // Load saved state from localStorage on initial render if (typeof window !== 'undefined') { const saved = localStorage.getItem('gt-sidebar-collapsed'); return saved ? JSON.parse(saved) : false; } return false; }); // Track if user has ever interacted with the sidebar const [hasUserInteracted, setHasUserInteracted] = useState(() => { if (typeof window !== 'undefined') { return localStorage.getItem('gt-sidebar-collapsed') !== null; } return false; }); const [showPulse, setShowPulse] = useState(false); const [isTabHovered, setIsTabHovered] = useState(false); const [isNarrowScreen, setIsNarrowScreen] = useState(false); const [userManuallyExpanded, setUserManuallyExpanded] = useState(false); // Fetch tenant display name from control panel (via Next.js API route) // Cache in sessionStorage to persist across navigation useEffect(() => { const fetchTenantDisplayName = async () => { if (typeof window === 'undefined') return; try { const localTenantInfo = getTenantInfo(); if (!localTenantInfo) return; // Check if we have cached tenant display name in sessionStorage const cachedTenantName = sessionStorage.getItem('gt2_tenant_display_name'); if (cachedTenantName) { // Use cached value immediately setTenantInfo({ domain: localTenantInfo.domain, name: cachedTenantName, id: localTenantInfo.id }); return; } // Fetch tenant display name via Next.js API route (avoids CORS) const response = await fetch('/api/tenant-info'); if (response.ok) { const data = await response.json(); const displayName = data.name || localTenantInfo.name; // Cache the display name in sessionStorage sessionStorage.setItem('gt2_tenant_display_name', displayName); // Update with correct display name from control panel setTenantInfo({ domain: localTenantInfo.domain, name: displayName, id: localTenantInfo.id }); } else { // If API fails, use localStorage value setTenantInfo(localTenantInfo); } } catch (error) { console.error('Failed to fetch tenant display name:', error); // On error, use localStorage value const tenant = getTenantInfo(); if (tenant) { setTenantInfo(tenant); } } }; fetchTenantDisplayName(); }, []); // Check screen width and auto-collapse on narrow screens useEffect(() => { const checkScreenWidth = () => { const screenWidth = window.innerWidth; const sidebarWidth = 320; // w-80 = 320px const minMainContentWidth = 400; // Minimum space needed for main content const collisionPoint = sidebarWidth + minMainContentWidth; // 720px const isNarrow = screenWidth < 1024; // lg breakpoint const isVeryNarrow = screenWidth < 768; // md breakpoint const wouldOverlap = screenWidth < collisionPoint; // Hard boundary at 720px setIsNarrowScreen(isNarrow); // Auto-collapse on narrow screens if user has never interacted with the sidebar if (isNarrow && !isCollapsed && !hasUserInteracted) { setIsCollapsed(true); } // Force collapse on very narrow screens regardless of user interaction if (isVeryNarrow && !isCollapsed) { setIsCollapsed(true); setUserManuallyExpanded(false); // Reset manual override on very narrow screens } // HARD BOUNDARY: Force collapse when sidebar would overlap main content if (wouldOverlap && !isCollapsed) { setIsCollapsed(true); setUserManuallyExpanded(false); // Reset manual override when hitting boundary } // Reset manual override when going back to wide screen if (!isNarrow) { setUserManuallyExpanded(false); } }; if (typeof window !== 'undefined') { // Only run checkScreenWidth on mount and resize, not on every render checkScreenWidth(); window.addEventListener('resize', checkScreenWidth); return () => window.removeEventListener('resize', checkScreenWidth); } }, []); // Remove dependencies to prevent running on state changes // Helper to check if any navigation page is active const isNavActive = ['/chat', '/agents', '/datasets', '/teams', '/observability'].some(path => pathname === path || pathname.startsWith(path + '/') ); // Profile menu is no longer used for navigation, only for sign out // Save collapse state to localStorage whenever it changes useEffect(() => { if (typeof window !== 'undefined') { localStorage.setItem('gt-sidebar-collapsed', JSON.stringify(isCollapsed)); onCollapseChange?.(isCollapsed); } }, [isCollapsed, onCollapseChange]); // Load user's available agents - ONLY on chat page for performance useEffect(() => { const loadUserAgents = async () => { try { const token = getAuthToken(); if (!token) return; // Use lightweight minimal endpoint for better performance const response = await fetch('/api/v1/agents/minimal', { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, }); if (response.ok) { const agents = await response.json(); console.log('🤖 Loaded minimal agents for filtering:', agents); setAvailableAgents(agents); } } catch (error) { console.error('Error loading user agents:', error); } }; // Load agents once on first mount, cache for entire session // No pathname check - agents needed for filter dropdown on all pages if (availableAgents.length === 0) { loadUserAgents(); } }, [availableAgents.length]); const handleCollapseToggle = () => { setShowPulse(true); const newCollapsedState = !isCollapsed; const screenWidth = window.innerWidth; const sidebarWidth = 320; // w-80 = 320px const minMainContentWidth = 400; // Minimum space needed for main content const collisionPoint = sidebarWidth + minMainContentWidth; // 720px const isVeryNarrow = screenWidth < 768; // md breakpoint const wouldOverlap = screenWidth < collisionPoint; // Hard boundary // Prevent expansion on very narrow screens if (!newCollapsedState && isVeryNarrow) { setTimeout(() => setShowPulse(false), 300); return; // Don't expand on very narrow screens } // HARD BOUNDARY: Prevent expansion when it would overlap main content if (!newCollapsedState && wouldOverlap) { setTimeout(() => setShowPulse(false), 300); return; // Don't expand when it would cause overlap } setIsCollapsed(newCollapsedState); setIsTabHovered(false); // Reset hover state when toggling setHasUserInteracted(true); // Mark that user has interacted with sidebar // If user is expanding on a narrow screen, mark as manual override if (!newCollapsedState && isNarrowScreen && !isVeryNarrow && !wouldOverlap) { setUserManuallyExpanded(true); } setTimeout(() => setShowPulse(false), 300); }; return (
{/* Collapsed Menu - Always visible when collapsed */}
{/* Logo with Expand Arrow */}
setIsTabHovered(true)} onMouseLeave={() => setIsTabHovered(false)} onClick={handleCollapseToggle} >
{/* GT Logo */} GT Logo {/* Arrow Reveal Overlay */}
{/* Main Navigation Icons */}
{/* Observability - All Users */}
{/* Conversation History Icon (when collapsed) */}
{/* Profile Icon */}
setShowUserMenu(!showUserMenu)} className="p-2 rounded-xl transition-all duration-200 backdrop-blur-md border bg-white/30 border-white/40 hover:bg-white/50 relative cursor-pointer" >
{user ? getInitials(user.full_name || user.email || '') : '?'}
{/* User Menu Dropdown for collapsed state */} {showUserMenu && (
)}
{/* Full Sidebar - Same interface for all screen sizes */}
{/* Header - Unified interface for all screen sizes */}
GT Edge AI Logo
{/* Collapse arrow - always visible on all screen sizes */}
{/* Tenant Name - Above Navigation */} {tenantInfo && (

{tenantInfo.name}

)} {/* Navigation */}
{/* Conversation History (All pages) */}
{/* Navigation Header */}
Conversations {/* Count will be updated by conversation component */} {/* Green pulse indicator when there are unread messages */} {Object.keys(unreadCounts).length > 0 && (
{Object.values(unreadCounts).reduce((sum, count) => sum + count, 0)}
)}
{/* Time Filter Dropdown */} window.dispatchEvent(new CustomEvent('filterTime', { detail: 'all' }))}> All Time window.dispatchEvent(new CustomEvent('filterTime', { detail: 'today' }))}> Today window.dispatchEvent(new CustomEvent('filterTime', { detail: 'week' }))}> This Week window.dispatchEvent(new CustomEvent('filterTime', { detail: 'month' }))}> This Month {/* Agent Filter Dropdown */} window.dispatchEvent(new CustomEvent('filterAgent', { detail: 'all' }))}> All Agents {availableAgents.map(agent => ( window.dispatchEvent(new CustomEvent('filterAgent', { detail: agent.id }))} > {agent.name} ))} {availableAgents.length === 0 && ( No agents found )}
{/* Conversation History Content */}
{ // Fallback: Navigate to chat page and load conversation window.location.href = `/chat?conversation=${conversationId}`; })} currentConversationId={undefined} />
{/* Footer */}
{/* User Profile Section */}
setShowUserMenu(!showUserMenu)} className="w-full flex items-center space-x-3 px-3 py-2 text-sm rounded-lg transition-colors cursor-pointer bg-gt-gray-100 border border-gt-gray-200 hover:bg-gt-gray-200" >
{user ? getInitials(user.full_name || user.email || '') : '?'}

{user?.full_name || 'Unknown User'}

{user?.user_type?.replace('_', ' ') || 'User'}

{/* User Dropdown Menu */} {showUserMenu && (
)}
{/* Version Info */}

GT AI OS Community | v2.0.33

{/* Overlay for user menu */} {showUserMenu && (
setShowUserMenu(false)} /> )}
); }