import { useEffect, useMemo, useRef, useState } from 'react'; import { api } from '@renderer/api'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs'; import { ExecutionSessionsSection } from './ExecutionSessionsSection'; import { isBoardTaskActivityUiEnabled, isBoardTaskExactLogsUiEnabled } from './featureGates'; import { TaskActivitySection } from './TaskActivitySection'; import { TaskLogStreamSection } from './TaskLogStreamSection'; import type { TeamTaskWithKanban } from '@shared/types'; type TaskLogsTab = 'activity' | 'stream' | 'sessions'; interface TaskLogsPanelProps { teamName: string; task: TeamTaskWithKanban; isOpen?: boolean; taskSince?: string; isExecutionRefreshing?: boolean; isExecutionPreviewOnline?: boolean; onRefreshingChange?: (isRefreshing: boolean) => void; showSubagentPreview?: boolean; showLeadPreview?: boolean; onPreviewOnlineChange?: (isOnline: boolean) => void; onTaskLogActivityChange?: (isActive: boolean) => void; } const TASK_LOG_ACTIVITY_PULSE_MS = 1800; export const TaskLogsPanel = ({ teamName, task, isOpen = true, taskSince, isExecutionRefreshing = false, isExecutionPreviewOnline = false, onRefreshingChange, showSubagentPreview = false, showLeadPreview = false, onPreviewOnlineChange, onTaskLogActivityChange, }: TaskLogsPanelProps): React.JSX.Element => { const availableTabs = useMemo(() => { const tabs: TaskLogsTab[] = []; if (isBoardTaskExactLogsUiEnabled()) { tabs.push('stream'); } if (isBoardTaskActivityUiEnabled()) { tabs.push('activity'); } tabs.push('sessions'); return tabs; }, []); const defaultTab = availableTabs[0] ?? 'sessions'; const [activeTab, setActiveTab] = useState(defaultTab); const [isTaskLogActivityActive, setIsTaskLogActivityActive] = useState(false); const [hasOpenedContent, setHasOpenedContent] = useState(isOpen); const pulseTimerRef = useRef | null>(null); const taskLogTrackingEnabled = task.status === 'in_progress' && availableTabs.includes('stream'); useEffect(() => { setActiveTab(defaultTab); }, [defaultTab, task.id]); useEffect(() => { if (!availableTabs.includes(activeTab)) { setActiveTab(defaultTab); } }, [activeTab, availableTabs, defaultTab]); useEffect(() => { if (isOpen) { setHasOpenedContent(true); } }, [isOpen]); useEffect(() => { onTaskLogActivityChange?.(isTaskLogActivityActive); }, [isTaskLogActivityActive, onTaskLogActivityChange]); useEffect(() => { if (pulseTimerRef.current) { clearTimeout(pulseTimerRef.current); pulseTimerRef.current = null; } setIsTaskLogActivityActive(false); }, [task.id]); useEffect(() => { if (!taskLogTrackingEnabled || !api.teams.setTaskLogStreamTracking) { return; } void Promise.resolve(api.teams.setTaskLogStreamTracking(teamName, true)).catch(() => undefined); return () => { void Promise.resolve(api.teams.setTaskLogStreamTracking(teamName, false)).catch( () => undefined ); }; }, [taskLogTrackingEnabled, teamName]); useEffect(() => { if (!taskLogTrackingEnabled) { if (pulseTimerRef.current) { clearTimeout(pulseTimerRef.current); pulseTimerRef.current = null; } setIsTaskLogActivityActive(false); return; } const unsubscribe = api.teams.onTeamChange?.((_event, event) => { if ( event.teamName !== teamName || event.type !== 'task-log-change' || event.taskId !== task.id ) { return; } setIsTaskLogActivityActive(true); if (pulseTimerRef.current) { clearTimeout(pulseTimerRef.current); } pulseTimerRef.current = setTimeout(() => { pulseTimerRef.current = null; setIsTaskLogActivityActive(false); }, TASK_LOG_ACTIVITY_PULSE_MS); }); return () => { if (pulseTimerRef.current) { clearTimeout(pulseTimerRef.current); pulseTimerRef.current = null; } if (typeof unsubscribe === 'function') { unsubscribe(); } }; }, [task.id, taskLogTrackingEnabled, teamName]); return ( setActiveTab(value as TaskLogsTab)} className="space-y-3" > {availableTabs.includes('stream') ? ( Task Log Stream ) : null} {availableTabs.includes('activity') ? ( Task Activity ) : null} Execution Sessions {availableTabs.includes('stream') && hasOpenedContent ? ( ) : null} {availableTabs.includes('activity') && hasOpenedContent ? ( ) : null} {hasOpenedContent ? ( ) : null} ); };