import { useState, useEffect, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { useStore } from '@nanostores/react'; import { Switch } from '@radix-ui/react-switch'; import * as RadixDialog from '@radix-ui/react-dialog'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { classNames } from '~/utils/classNames'; import { TabManagement } from './developer/TabManagement'; import { TabTile } from './shared/TabTile'; import { useUpdateCheck } from '~/lib/hooks/useUpdateCheck'; import { useFeatures } from '~/lib/hooks/useFeatures'; import { useNotifications } from '~/lib/hooks/useNotifications'; import { useConnectionStatus } from '~/lib/hooks/useConnectionStatus'; import { useDebugStatus } from '~/lib/hooks/useDebugStatus'; import { tabConfigurationStore, developerModeStore, setDeveloperMode } from '~/lib/stores/settings'; import type { TabType, TabVisibilityConfig } from './settings.types'; import { TAB_LABELS, DEFAULT_TAB_CONFIG } from './settings.types'; import { resetTabConfiguration } from '~/lib/stores/settings'; import { DialogTitle } from '~/components/ui/Dialog'; import { useDrag, useDrop } from 'react-dnd'; // Import all tab components import ProfileTab from './profile/ProfileTab'; import SettingsTab from './settings/SettingsTab'; import NotificationsTab from './notifications/NotificationsTab'; import FeaturesTab from './features/FeaturesTab'; import DataTab from './data/DataTab'; import DebugTab from './debug/DebugTab'; import { EventLogsTab } from './event-logs/EventLogsTab'; import UpdateTab from './update/UpdateTab'; import ConnectionsTab from './connections/ConnectionsTab'; import CloudProvidersTab from './providers/CloudProvidersTab'; import ServiceStatusTab from './providers/ServiceStatusTab'; import LocalProvidersTab from './providers/LocalProvidersTab'; import TaskManagerTab from './task-manager/TaskManagerTab'; interface ControlPanelProps { open: boolean; onClose: () => void; } interface TabWithDevType extends TabVisibilityConfig { isExtraDevTab?: boolean; } const TAB_DESCRIPTIONS: Record = { profile: 'Manage your profile and account settings', settings: 'Configure application preferences', notifications: 'View and manage your notifications', features: 'Explore new and upcoming features', data: 'Manage your data and storage', 'cloud-providers': 'Configure cloud AI providers and models', 'local-providers': 'Configure local AI providers and models', 'service-status': 'Monitor cloud LLM service status', connection: 'Check connection status and settings', debug: 'Debug tools and system information', 'event-logs': 'View system events and logs', update: 'Check for updates and release notes', 'task-manager': 'Monitor system resources and processes', }; // Add DraggableTabTile component before the ControlPanel component const DraggableTabTile = ({ tab, index, moveTab, ...props }: { tab: TabWithDevType; index: number; moveTab: (dragIndex: number, hoverIndex: number) => void; onClick: () => void; isActive: boolean; hasUpdate: boolean; statusMessage: string; description: string; isLoading?: boolean; }) => { const [{ isDragging }, drag] = useDrag({ type: 'tab', item: { index, id: tab.id }, collect: (monitor) => ({ isDragging: monitor.isDragging(), }), }); const [{ isOver, canDrop }, drop] = useDrop({ accept: 'tab', hover: (item: { index: number; id: string }, monitor) => { if (!monitor.isOver({ shallow: true })) { return; } if (item.id === tab.id) { return; } if (item.index === index) { return; } // Only move when hovering over the middle section const hoverBoundingRect = monitor.getSourceClientOffset(); const clientOffset = monitor.getClientOffset(); if (!hoverBoundingRect || !clientOffset) { return; } const hoverMiddleX = hoverBoundingRect.x + 150; // Half of typical card width const hoverClientX = clientOffset.x; // Only perform the move when the mouse has crossed half of the items width if (item.index < index && hoverClientX < hoverMiddleX) { return; } if (item.index > index && hoverClientX > hoverMiddleX) { return; } moveTab(item.index, index); item.index = index; }, collect: (monitor) => ({ isOver: monitor.isOver({ shallow: true }), canDrop: monitor.canDrop(), }), }); const dropIndicatorClasses = classNames('rounded-xl border-2 border-transparent transition-all duration-200', { 'ring-2 ring-purple-500 ring-opacity-50 bg-purple-50 dark:bg-purple-900/20': isOver, 'hover:ring-2 hover:ring-purple-500/30': canDrop && !isOver, }); return ( drag(drop(node))} style={{ opacity: isDragging ? 0.5 : 1, cursor: 'move', position: 'relative', zIndex: isDragging ? 100 : isOver ? 50 : 1, }} animate={{ scale: isDragging ? 1.02 : isOver ? 1.05 : 1, boxShadow: isDragging ? '0 8px 24px rgba(0, 0, 0, 0.15)' : isOver ? '0 4px 12px rgba(147, 51, 234, 0.3)' : '0 0 0 rgba(0, 0, 0, 0)', borderColor: isOver ? 'rgb(147, 51, 234)' : isDragging ? 'rgba(147, 51, 234, 0.5)' : 'transparent', y: isOver ? -2 : 0, }} transition={{ type: 'spring', stiffness: 500, damping: 30, mass: 0.8, }} className={dropIndicatorClasses} > {isOver && (
)} ); }; export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { // State const [activeTab, setActiveTab] = useState(null); const [loadingTab, setLoadingTab] = useState(null); const [showTabManagement, setShowTabManagement] = useState(false); const [profile, setProfile] = useState({ avatar: null, notifications: true }); // Store values const tabConfiguration = useStore(tabConfigurationStore); const developerMode = useStore(developerModeStore); // Status hooks const { hasUpdate, currentVersion, acknowledgeUpdate } = useUpdateCheck(); const { hasNewFeatures, unviewedFeatures, acknowledgeAllFeatures } = useFeatures(); const { hasUnreadNotifications, unreadNotifications, markAllAsRead } = useNotifications(); const { hasConnectionIssues, currentIssue, acknowledgeIssue } = useConnectionStatus(); const { hasActiveWarnings, activeIssues, acknowledgeAllIssues } = useDebugStatus(); // Initialize profile from localStorage on mount useEffect(() => { if (typeof window === 'undefined') { return; } const saved = localStorage.getItem('bolt_user_profile'); if (saved) { try { const parsedProfile = JSON.parse(saved); setProfile(parsedProfile); } catch (error) { console.warn('Failed to parse profile from localStorage:', error); } } }, []); // Add visibleTabs logic using useMemo const visibleTabs = useMemo(() => { if (!tabConfiguration?.userTabs || !Array.isArray(tabConfiguration.userTabs)) { console.warn('Invalid tab configuration, resetting to defaults'); resetTabConfiguration(); return []; } // In developer mode, show ALL tabs without restrictions if (developerMode) { // Combine all unique tabs from both user and developer configurations const allTabs = new Set([ ...DEFAULT_TAB_CONFIG.map((tab) => tab.id), ...tabConfiguration.userTabs.map((tab) => tab.id), ...(tabConfiguration.developerTabs || []).map((tab) => tab.id), ]); // Create a complete tab list with all tabs visible const devTabs = Array.from(allTabs).map((tabId) => { // Try to find existing configuration for this tab const existingTab = tabConfiguration.developerTabs?.find((t) => t.id === tabId) || tabConfiguration.userTabs?.find((t) => t.id === tabId) || DEFAULT_TAB_CONFIG.find((t) => t.id === tabId); return { id: tabId, visible: true, window: 'developer' as const, order: existingTab?.order || DEFAULT_TAB_CONFIG.findIndex((t) => t.id === tabId), }; }); return devTabs.sort((a, b) => a.order - b.order); } // In user mode, only show visible user tabs return tabConfiguration.userTabs .filter((tab) => { if (!tab || typeof tab.id !== 'string') { console.warn('Invalid tab entry:', tab); return false; } // Hide notifications tab if notifications are disabled if (tab.id === 'notifications' && !profile.notifications) { return false; } // Only show tabs that are explicitly visible and assigned to the user window return tab.visible && tab.window === 'user'; }) .sort((a, b) => a.order - b.order); }, [tabConfiguration, profile.notifications, developerMode]); // Add moveTab handler const moveTab = (dragIndex: number, hoverIndex: number) => { const newTabs = [...visibleTabs]; const dragTab = newTabs[dragIndex]; newTabs.splice(dragIndex, 1); newTabs.splice(hoverIndex, 0, dragTab); // Update the order of the tabs const updatedTabs = newTabs.map((tab, index) => ({ ...tab, order: index, window: 'developer' as const, visible: true, })); // Update the tab configuration store directly if (developerMode) { // In developer mode, update developerTabs while preserving configuration tabConfigurationStore.set({ ...tabConfiguration, developerTabs: updatedTabs, }); } else { // In user mode, update userTabs tabConfigurationStore.set({ ...tabConfiguration, userTabs: updatedTabs.map((tab) => ({ ...tab, window: 'user' as const })), }); } }; // Handlers const handleBack = () => { if (showTabManagement) { setShowTabManagement(false); } else if (activeTab) { setActiveTab(null); } }; const handleDeveloperModeChange = (checked: boolean) => { console.log('Developer mode changed:', checked); setDeveloperMode(checked); }; // Add effect to log developer mode changes useEffect(() => { console.log('Current developer mode:', developerMode); }, [developerMode]); const getTabComponent = () => { switch (activeTab) { case 'profile': return ; case 'settings': return ; case 'notifications': return ; case 'features': return ; case 'data': return ; case 'cloud-providers': return ; case 'local-providers': return ; case 'connection': return ; case 'debug': return ; case 'event-logs': return ; case 'update': return ; case 'task-manager': return ; case 'service-status': return ; default: return null; } }; const getTabUpdateStatus = (tabId: TabType): boolean => { switch (tabId) { case 'update': return hasUpdate; case 'features': return hasNewFeatures; case 'notifications': return hasUnreadNotifications; case 'connection': return hasConnectionIssues; case 'debug': return hasActiveWarnings; default: return false; } }; const getStatusMessage = (tabId: TabType): string => { switch (tabId) { case 'update': return `New update available (v${currentVersion})`; case 'features': return `${unviewedFeatures.length} new feature${unviewedFeatures.length === 1 ? '' : 's'} to explore`; case 'notifications': return `${unreadNotifications.length} unread notification${unreadNotifications.length === 1 ? '' : 's'}`; case 'connection': return currentIssue === 'disconnected' ? 'Connection lost' : currentIssue === 'high-latency' ? 'High latency detected' : 'Connection issues detected'; case 'debug': { const warnings = activeIssues.filter((i) => i.type === 'warning').length; const errors = activeIssues.filter((i) => i.type === 'error').length; return `${warnings} warning${warnings === 1 ? '' : 's'}, ${errors} error${errors === 1 ? '' : 's'}`; } default: return ''; } }; const handleTabClick = (tabId: TabType) => { setLoadingTab(tabId); setActiveTab(tabId); // Acknowledge notifications based on tab switch (tabId) { case 'update': acknowledgeUpdate(); break; case 'features': acknowledgeAllFeatures(); break; case 'notifications': markAllAsRead(); break; case 'connection': acknowledgeIssue(); break; case 'debug': acknowledgeAllIssues(); break; } // Clear loading state after a delay setTimeout(() => setLoadingTab(null), 500); }; return (
{/* Header */}
{activeTab || showTabManagement ? ( ) : ( )} {showTabManagement ? 'Tab Management' : activeTab ? TAB_LABELS[activeTab] : 'Control Panel'}
{/* Only show Manage Tabs button in developer mode */} {!activeTab && !showTabManagement && developerMode && ( setShowTabManagement(true)} className="flex items-center space-x-2 px-3 py-1.5 rounded-lg bg-gray-100 dark:bg-gray-800 hover:bg-purple-500/10 dark:hover:bg-purple-500/20 group transition-all duration-200" whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} >
Manage Tabs )}
Toggle developer mode
{/* Content */}
{showTabManagement ? ( ) : activeTab ? ( getTabComponent() ) : ( {visibleTabs.map((tab: TabWithDevType, index: number) => ( handleTabClick(tab.id)} isActive={activeTab === tab.id} hasUpdate={getTabUpdateStatus(tab.id)} statusMessage={getStatusMessage(tab.id)} description={TAB_DESCRIPTIONS[tab.id]} isLoading={loadingTab === tab.id} /> ))} )}
); };