/* * MainContent.jsx - Main Content Area with Session Protection Props Passthrough * * SESSION PROTECTION PASSTHROUGH: * =============================== * * This component serves as a passthrough layer for Session Protection functions: * - Receives session management functions from App.jsx * - Passes them down to ChatInterface.jsx * * No session protection logic is implemented here - it's purely a props bridge. */ import React, { useState, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import ChatInterface from './ChatInterface'; import FileTree from './FileTree'; import CodeEditor from './CodeEditor'; import StandaloneShell from './StandaloneShell'; import GitPanel from './GitPanel'; import ErrorBoundary from './ErrorBoundary'; import ClaudeLogo from './ClaudeLogo'; import CursorLogo from './CursorLogo'; import TaskList from './TaskList'; import TaskDetail from './TaskDetail'; import PRDEditor from './PRDEditor'; import Tooltip from './Tooltip'; import { useTaskMaster } from '../contexts/TaskMasterContext'; import { useTasksSettings } from '../contexts/TasksSettingsContext'; import { api } from '../utils/api'; function MainContent({ selectedProject, selectedSession, activeTab, setActiveTab, ws, sendMessage, messages, isMobile, isPWA, onMenuClick, isLoading, onInputFocusChange, // Session Protection Props: Functions passed down from App.jsx to manage active session state // These functions control when project updates are paused during active conversations onSessionActive, // Mark session as active when user sends message onSessionInactive, // Mark session as inactive when conversation completes/aborts onSessionProcessing, // Mark session as processing (thinking/working) onSessionNotProcessing, // Mark session as not processing (finished thinking) processingSessions, // Set of session IDs currently processing onReplaceTemporarySession, // Replace temporary session ID with real session ID from WebSocket onNavigateToSession, // Navigate to a specific session (for Claude CLI session duplication workaround) onShowSettings, // Show tools settings panel autoExpandTools, // Auto-expand tool accordions showRawParameters, // Show raw parameters in tool accordions showThinking, // Show thinking/reasoning sections autoScrollToBottom, // Auto-scroll to bottom when new messages arrive sendByCtrlEnter, // Send by Ctrl+Enter mode for East Asian language input externalMessageUpdate // Trigger for external CLI updates to current session }) { const { t } = useTranslation(); const [editingFile, setEditingFile] = useState(null); const [selectedTask, setSelectedTask] = useState(null); const [showTaskDetail, setShowTaskDetail] = useState(false); const [editorWidth, setEditorWidth] = useState(600); const [isResizing, setIsResizing] = useState(false); const [editorExpanded, setEditorExpanded] = useState(false); const resizeRef = useRef(null); // PRD Editor state const [showPRDEditor, setShowPRDEditor] = useState(false); const [selectedPRD, setSelectedPRD] = useState(null); const [existingPRDs, setExistingPRDs] = useState([]); const [prdNotification, setPRDNotification] = useState(null); // TaskMaster context const { tasks, currentProject, refreshTasks, setCurrentProject } = useTaskMaster(); const { tasksEnabled, isTaskMasterInstalled, isTaskMasterReady } = useTasksSettings(); // Only show tasks tab if TaskMaster is installed and enabled const shouldShowTasksTab = tasksEnabled && isTaskMasterInstalled; // Sync selectedProject with TaskMaster context useEffect(() => { if (selectedProject && selectedProject !== currentProject) { setCurrentProject(selectedProject); } }, [selectedProject, currentProject, setCurrentProject]); // Switch away from tasks tab when tasks are disabled or TaskMaster is not installed useEffect(() => { if (!shouldShowTasksTab && activeTab === 'tasks') { setActiveTab('chat'); } }, [shouldShowTasksTab, activeTab, setActiveTab]); // Load existing PRDs when current project changes useEffect(() => { const loadExistingPRDs = async () => { if (!currentProject?.name) { setExistingPRDs([]); return; } try { const response = await api.get(`/taskmaster/prd/${encodeURIComponent(currentProject.name)}`); if (response.ok) { const data = await response.json(); setExistingPRDs(data.prdFiles || []); } else { setExistingPRDs([]); } } catch (error) { console.error('Failed to load existing PRDs:', error); setExistingPRDs([]); } }; loadExistingPRDs(); }, [currentProject?.name]); const handleFileOpen = (filePath, diffInfo = null) => { // Create a file object that CodeEditor expects const file = { name: filePath.split('/').pop(), path: filePath, projectName: selectedProject?.name, diffInfo: diffInfo // Pass along diff information if available }; setEditingFile(file); }; const handleCloseEditor = () => { setEditingFile(null); setEditorExpanded(false); }; const handleToggleEditorExpand = () => { setEditorExpanded(!editorExpanded); }; const handleTaskClick = (task) => { // If task is just an ID (from dependency click), find the full task object if (typeof task === 'object' && task.id && !task.title) { const fullTask = tasks?.find(t => t.id === task.id); if (fullTask) { setSelectedTask(fullTask); setShowTaskDetail(true); } } else { setSelectedTask(task); setShowTaskDetail(true); } }; const handleTaskDetailClose = () => { setShowTaskDetail(false); setSelectedTask(null); }; const handleTaskStatusChange = (taskId, newStatus) => { // This would integrate with TaskMaster API to update task status console.log('Update task status:', taskId, newStatus); refreshTasks?.(); }; // Handle resize functionality const handleMouseDown = (e) => { if (isMobile) return; // Disable resize on mobile setIsResizing(true); e.preventDefault(); }; useEffect(() => { const handleMouseMove = (e) => { if (!isResizing) return; const container = resizeRef.current?.parentElement; if (!container) return; const containerRect = container.getBoundingClientRect(); const newWidth = containerRect.right - e.clientX; // Min width: 300px, Max width: 80% of container const minWidth = 300; const maxWidth = containerRect.width * 0.8; if (newWidth >= minWidth && newWidth <= maxWidth) { setEditorWidth(newWidth); } }; const handleMouseUp = () => { setIsResizing(false); }; if (isResizing) { document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; } return () => { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); document.body.style.cursor = ''; document.body.style.userSelect = ''; }; }, [isResizing]); if (isLoading) { return (
{t('mainContent.settingUpWorkspace')}
{t('mainContent.selectProjectDescription')}
💡 {t('mainContent.tip')}: {isMobile ? t('mainContent.createProjectMobile') : t('mainContent.createProjectDesktop')}