import React, { Suspense, lazy, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { useTabState } from '@/hooks/useTabState'; import { useScreenTracking } from '@/hooks/useAnalytics'; import { Tab } from '@/contexts/TabContext'; import { Loader2, Plus } from 'lucide-react'; import { api, type Project, type Session, type ClaudeMdFile } from '@/lib/api'; import { ProjectList } from '@/components/ProjectList'; import { SessionList } from '@/components/SessionList'; import { RunningClaudeSessions } from '@/components/RunningClaudeSessions'; import { Button } from '@/components/ui/button'; import { useTranslation } from '@/hooks/useTranslation'; // Lazy load heavy components const ClaudeCodeSession = lazy(() => import('@/components/ClaudeCodeSession').then(m => ({ default: m.ClaudeCodeSession }))); const AgentRunOutputViewer = lazy(() => import('@/components/AgentRunOutputViewer')); const AgentExecution = lazy(() => import('@/components/AgentExecution').then(m => ({ default: m.AgentExecution }))); const CreateAgent = lazy(() => import('@/components/CreateAgent').then(m => ({ default: m.CreateAgent }))); const UsageDashboard = lazy(() => import('@/components/UsageDashboard').then(m => ({ default: m.UsageDashboard }))); const MCPManager = lazy(() => import('@/components/MCPManager').then(m => ({ default: m.MCPManager }))); const Settings = lazy(() => import('@/components/Settings').then(m => ({ default: m.Settings }))); const MarkdownEditor = lazy(() => import('@/components/MarkdownEditor').then(m => ({ default: m.MarkdownEditor }))); // const ClaudeFileEditor = lazy(() => import('@/components/ClaudeFileEditor').then(m => ({ default: m.ClaudeFileEditor }))); // Import non-lazy components for projects view interface TabPanelProps { tab: Tab; isActive: boolean; } const TabPanel: React.FC = ({ tab, isActive }) => { const { t } = useTranslation(); const { updateTab, createChatTab } = useTabState(); const [projects, setProjects] = React.useState([]); const [selectedProject, setSelectedProject] = React.useState(null); const [sessions, setSessions] = React.useState([]); const [loading, setLoading] = React.useState(false); // Track screen when tab becomes active useScreenTracking(isActive ? tab.type : undefined, isActive ? tab.id : undefined); const [error, setError] = React.useState(null); // Load projects when tab becomes active and is of type 'projects' useEffect(() => { if (isActive && tab.type === 'projects') { loadProjects(); } }, [isActive, tab.type]); const loadProjects = async () => { try { setLoading(true); setError(null); const projectList = await api.listProjects(); setProjects(projectList); } catch (err) { console.error("Failed to load projects:", err); setError(t('failedToLoadProjects')); } finally { setLoading(false); } }; const handleProjectClick = async (project: Project) => { try { setLoading(true); setError(null); const sessionList = await api.getProjectSessions(project.id); setSessions(sessionList); setSelectedProject(project); } catch (err) { console.error("Failed to load sessions:", err); setError(t('failedToLoadSessions')); } finally { setLoading(false); } }; const handleBack = () => { setSelectedProject(null); setSessions([]); }; const handleNewSession = () => { // Create a new chat tab createChatTab(); }; // Panel visibility - hide when not active const panelVisibilityClass = isActive ? "" : "hidden"; const renderContent = () => { switch (tab.type) { case 'projects': return (
{/* Header */}

{t('ccProjects')}

{t('browseClaudeCodeSessions')}

{/* Error display */} {error && ( {error} )} {/* Loading state */} {loading && (
)} {/* Content */} {!loading && ( {selectedProject ? ( { // Update tab to show this session updateTab(tab.id, { type: 'chat', title: session.project_path.split('/').pop() || 'Session', sessionId: session.id, sessionData: session, // Store full session object initialProjectPath: session.project_path, }); }} onEditClaudeFile={(file: ClaudeMdFile) => { // Open CLAUDE.md file in a new tab window.dispatchEvent(new CustomEvent('open-claude-file', { detail: { file } })); }} /> ) : ( {/* New session button at the top */} {/* Running Claude Sessions */} {/* Project list */} {projects.length > 0 ? ( { // Project settings functionality can be added here if needed console.log('Project settings clicked for:', project); }} loading={loading} className="animate-fade-in" /> ) : (

{t('noProjectsFound')}

)}
)}
)}
); case 'chat': return ( { // Go back to projects view in the same tab updateTab(tab.id, { type: 'projects', title: t('ccProjects'), }); }} /> ); case 'agent': if (!tab.agentRunId) { return
{t('messages.noAgentRunIdSpecified')}
; } return ( ); case 'usage': return {}} />; case 'mcp': return {}} />; case 'settings': return {}} />; case 'claude-md': return {}} />; case 'claude-file': if (!tab.claudeFileId) { return
{t('messages.noClaudeFileIdSpecified')}
; } // Note: We need to get the actual file object for ClaudeFileEditor // For now, returning a placeholder return
{t('messages.claudeFileEditorNotImplemented')}
; case 'agent-execution': if (!tab.agentData) { return
{t('messages.noAgentDataSpecified')}
; } return ( {}} /> ); case 'create-agent': return ( { // Close this tab after agent is created window.dispatchEvent(new CustomEvent('close-tab', { detail: { tabId: tab.id } })); }} onBack={() => { // Close this tab when back is clicked window.dispatchEvent(new CustomEvent('close-tab', { detail: { tabId: tab.id } })); }} /> ); case 'import-agent': // TODO: Implement import agent component return
{t('messages.importAgentComingSoon')}
; default: return
{t('messages.unknownTabType')}: {tab.type}
; } }; return ( } > {renderContent()} ); }; export const TabContent: React.FC = () => { const { t } = useTranslation(); const { tabs, activeTabId, createChatTab, findTabBySessionId, createClaudeFileTab, createAgentExecutionTab, createCreateAgentTab, createImportAgentTab, closeTab, updateTab } = useTabState(); const [hasInitialized, setHasInitialized] = React.useState(false); // Auto redirect to home when no tabs (but not on initial load) useEffect(() => { if (hasInitialized && tabs.length === 0) { // Dispatch event to switch back to welcome view setTimeout(() => { window.dispatchEvent(new CustomEvent('switch-to-welcome')); }, 100); } }, [tabs.length, hasInitialized]); // Mark as initialized after first render useEffect(() => { setHasInitialized(true); }, []); // Listen for events to open sessions in tabs useEffect(() => { const handleOpenSessionInTab = (event: CustomEvent) => { const { session } = event.detail; // Check if tab already exists for this session const existingTab = findTabBySessionId(session.id); if (existingTab) { // Update existing tab with session data and switch to it updateTab(existingTab.id, { sessionData: session, title: session.project_path.split('/').pop() || 'Session' }); window.dispatchEvent(new CustomEvent('switch-to-tab', { detail: { tabId: existingTab.id } })); } else { // Create new tab for this session const projectName = session.project_path.split('/').pop() || 'Session'; const newTabId = createChatTab(session.id, projectName); // Update the new tab with session data updateTab(newTabId, { sessionData: session, initialProjectPath: session.project_path }); } }; const handleOpenClaudeFile = (event: CustomEvent) => { const { file } = event.detail; createClaudeFileTab(file.id, file.name || 'CLAUDE.md'); }; const handleOpenAgentExecution = (event: CustomEvent) => { const { agent, tabId } = event.detail; createAgentExecutionTab(agent, tabId); }; const handleOpenCreateAgentTab = () => { createCreateAgentTab(); }; const handleOpenImportAgentTab = () => { createImportAgentTab(); }; const handleCloseTab = (event: CustomEvent) => { const { tabId } = event.detail; closeTab(tabId); }; const handleClaudeSessionSelected = (event: CustomEvent) => { const { session } = event.detail; // Reuse same logic as handleOpenSessionInTab const existingTab = findTabBySessionId(session.id); if (existingTab) { updateTab(existingTab.id, { sessionData: session, title: session.project_path.split('/').pop() || 'Session', }); window.dispatchEvent(new CustomEvent('switch-to-tab', { detail: { tabId: existingTab.id } })); } else { const projectName = session.project_path.split('/').pop() || 'Session'; const newTabId = createChatTab(session.id, projectName); updateTab(newTabId, { sessionData: session, initialProjectPath: session.project_path, }); } }; window.addEventListener('open-session-in-tab', handleOpenSessionInTab as EventListener); window.addEventListener('open-claude-file', handleOpenClaudeFile as EventListener); window.addEventListener('open-agent-execution', handleOpenAgentExecution as EventListener); window.addEventListener('open-create-agent-tab', handleOpenCreateAgentTab); window.addEventListener('open-import-agent-tab', handleOpenImportAgentTab); window.addEventListener('close-tab', handleCloseTab as EventListener); window.addEventListener('claude-session-selected', handleClaudeSessionSelected as EventListener); return () => { window.removeEventListener('open-session-in-tab', handleOpenSessionInTab as EventListener); window.removeEventListener('open-claude-file', handleOpenClaudeFile as EventListener); window.removeEventListener('open-agent-execution', handleOpenAgentExecution as EventListener); window.removeEventListener('open-create-agent-tab', handleOpenCreateAgentTab); window.removeEventListener('open-import-agent-tab', handleOpenImportAgentTab); window.removeEventListener('close-tab', handleCloseTab as EventListener); window.removeEventListener('claude-session-selected', handleClaudeSessionSelected as EventListener); }; }, [createChatTab, findTabBySessionId, createClaudeFileTab, createAgentExecutionTab, createCreateAgentTab, createImportAgentTab, closeTab, updateTab]); return (
{tabs.map((tab) => ( ))} {tabs.length === 0 && (

{t('messages.noTabsOpen')}

{t('messages.clickPlusToStartChat')}

)}
); }; export default TabContent;