import React, { useState, useRef, useEffect } from 'react'; import { motion, AnimatePresence, Reorder } from 'framer-motion'; import { X, Plus, MessageSquare, Bot, AlertCircle, Loader2, Folder, BarChart, Server, Settings, FileText } from 'lucide-react'; import { useTabState } from '@/hooks/useTabState'; import { Tab, useTabContext } from '@/contexts/TabContext'; import { cn } from '@/lib/utils'; import { useTrackEvent } from '@/hooks'; interface TabItemProps { tab: Tab; isActive: boolean; onClose: (id: string) => void; onClick: (id: string) => void; isDragging?: boolean; setDraggedTabId?: (id: string | null) => void; } const TabItem: React.FC = ({ tab, isActive, onClose, onClick, isDragging = false, setDraggedTabId }) => { const [isHovered, setIsHovered] = useState(false); const getIcon = () => { switch (tab.type) { case 'chat': return MessageSquare; case 'agent': return Bot; case 'projects': return Folder; case 'usage': return BarChart; case 'mcp': return Server; case 'settings': return Settings; case 'claude-md': case 'claude-file': return FileText; case 'agent-execution': return Bot; case 'create-agent': return Plus; case 'import-agent': return Plus; default: return MessageSquare; } }; const getStatusIcon = () => { switch (tab.status) { case 'running': return ; case 'error': return ; default: return null; } }; const Icon = getIcon(); const statusIcon = getStatusIcon(); return ( setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} onClick={() => onClick(tab.id)} onDragStart={() => setDraggedTabId?.(tab.id)} onDragEnd={() => setDraggedTabId?.(null)} > {/* Tab Icon */}
{/* Tab Title */} {tab.title} {/* Status Indicators - always takes up space */}
{statusIcon && ( {statusIcon} )} {tab.hasUnsavedChanges && !statusIcon && ( )}
{/* Close Button - Always reserves space */}
); }; interface TabManagerProps { className?: string; } export const TabManager: React.FC = ({ className }) => { const { tabs, activeTabId, createChatTab, createProjectsTab, closeTab, switchToTab, updateTab, canAddTab } = useTabState(); // Access reorderTabs from context const { reorderTabs } = useTabContext(); const scrollContainerRef = useRef(null); const [showLeftScroll, setShowLeftScroll] = useState(false); const [showRightScroll, setShowRightScroll] = useState(false); const [draggedTabId, setDraggedTabId] = useState(null); // Analytics tracking const trackEvent = useTrackEvent(); // Listen for tab switch events useEffect(() => { const handleSwitchToTab = (event: CustomEvent) => { const { tabId } = event.detail; switchToTab(tabId); }; window.addEventListener('switch-to-tab', handleSwitchToTab as EventListener); return () => { window.removeEventListener('switch-to-tab', handleSwitchToTab as EventListener); }; }, [switchToTab]); // Listen for keyboard shortcut events useEffect(() => { const handleCreateTab = () => { createChatTab(); trackEvent.tabCreated('chat'); }; const handleCloseTab = async () => { if (activeTabId) { const tab = tabs.find(t => t.id === activeTabId); if (tab) { trackEvent.tabClosed(tab.type); } await closeTab(activeTabId); } }; const handleNextTab = () => { const currentIndex = tabs.findIndex(tab => tab.id === activeTabId); const nextIndex = (currentIndex + 1) % tabs.length; if (tabs[nextIndex]) { switchToTab(tabs[nextIndex].id); } }; const handlePreviousTab = () => { const currentIndex = tabs.findIndex(tab => tab.id === activeTabId); const previousIndex = currentIndex === 0 ? tabs.length - 1 : currentIndex - 1; if (tabs[previousIndex]) { switchToTab(tabs[previousIndex].id); } }; const handleTabByIndex = (event: CustomEvent) => { const { index } = event.detail; if (tabs[index]) { switchToTab(tabs[index].id); } }; const handleOpenSessionTab = (event: CustomEvent) => { const { session, projectPath } = event.detail; if (session && canAddTab()) { // Create a new chat tab with the session data const tabId = createChatTab(); // Update the tab with session data setTimeout(() => { updateTab(tabId, { type: 'chat', title: session.project_path.split('/').pop() || 'Session', sessionId: session.id, sessionData: session, initialProjectPath: projectPath || session.project_path, }); }, 100); } }; window.addEventListener('create-chat-tab', handleCreateTab); window.addEventListener('close-current-tab', handleCloseTab); window.addEventListener('switch-to-next-tab', handleNextTab); window.addEventListener('switch-to-previous-tab', handlePreviousTab); window.addEventListener('switch-to-tab-by-index', handleTabByIndex as EventListener); window.addEventListener('open-session-tab', handleOpenSessionTab as EventListener); return () => { window.removeEventListener('create-chat-tab', handleCreateTab); window.removeEventListener('close-current-tab', handleCloseTab); window.removeEventListener('switch-to-next-tab', handleNextTab); window.removeEventListener('switch-to-previous-tab', handlePreviousTab); window.removeEventListener('switch-to-tab-by-index', handleTabByIndex as EventListener); window.removeEventListener('open-session-tab', handleOpenSessionTab as EventListener); }; }, [tabs, activeTabId, createChatTab, closeTab, switchToTab, updateTab, canAddTab]); // Check scroll buttons visibility const checkScrollButtons = () => { const container = scrollContainerRef.current; if (!container) return; const { scrollLeft, scrollWidth, clientWidth } = container; setShowLeftScroll(scrollLeft > 0); setShowRightScroll(scrollLeft + clientWidth < scrollWidth - 1); }; useEffect(() => { checkScrollButtons(); const container = scrollContainerRef.current; if (!container) return; container.addEventListener('scroll', checkScrollButtons); window.addEventListener('resize', checkScrollButtons); return () => { container.removeEventListener('scroll', checkScrollButtons); window.removeEventListener('resize', checkScrollButtons); }; }, [tabs]); const handleReorder = (newOrder: Tab[]) => { // Find the positions that changed const oldOrder = tabs.map(tab => tab.id); const newOrderIds = newOrder.map(tab => tab.id); // Find what moved const movedTabId = newOrderIds.find((id, index) => oldOrder[index] !== id); if (!movedTabId) return; const oldIndex = oldOrder.indexOf(movedTabId); const newIndex = newOrderIds.indexOf(movedTabId); if (oldIndex !== -1 && newIndex !== -1 && oldIndex !== newIndex) { // Use the context's reorderTabs function reorderTabs(oldIndex, newIndex); // Track the reorder event trackEvent.featureUsed?.('tab_reorder', 'drag_drop', { from_index: oldIndex, to_index: newIndex }); } }; const handleCloseTab = async (id: string) => { const tab = tabs.find(t => t.id === id); if (tab) { trackEvent.tabClosed(tab.type); } await closeTab(id); }; const handleNewTab = () => { if (canAddTab()) { createProjectsTab(); trackEvent.tabCreated('projects'); } }; const scrollTabs = (direction: 'left' | 'right') => { const container = scrollContainerRef.current; if (!container) return; const scrollAmount = 200; const newScrollLeft = direction === 'left' ? container.scrollLeft - scrollAmount : container.scrollLeft + scrollAmount; container.scrollTo({ left: newScrollLeft, behavior: 'smooth' }); }; return (
{/* Left fade gradient */} {showLeftScroll && (
)} {/* Left scroll button */} {showLeftScroll && ( scrollTabs('left')} className={cn( "p-1.5 hover:bg-muted/80 rounded-sm z-20 ml-1", "transition-colors duration-200 flex items-center justify-center", "bg-background/98 backdrop-blur-xl backdrop-saturate-[1.8] shadow-sm border border-border/60" )} title="Scroll tabs left" > )} {/* Tabs container */}
{tabs.map((tab) => ( ))}
{/* Right fade gradient */} {showRightScroll && (
)} {/* Right scroll button */} {showRightScroll && ( scrollTabs('right')} className={cn( "p-1.5 hover:bg-muted/80 rounded-sm z-20 mr-1", "transition-colors duration-200 flex items-center justify-center", "bg-background/98 backdrop-blur-xl backdrop-saturate-[1.8] shadow-sm border border-border/60" )} title="Scroll tabs right" > )} {/* New tab button */}
); }; export default TabManager;