import React, { useState, useEffect } from 'react'; import { Box, Text, Newline, useInput } from 'ink'; import Spinner from 'ink-spinner'; import { getPersistenceManager } from '../../core/global-initialization.ts'; import type { PersistedTask } from '../../../core/persistence.ts'; interface Task { id: string; type: string; description: string; status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'cancelled'; priority: number; assignedAgent?: string; progress: number; error?: string; createdAt: Date; completedAt?: Date; dependencies: string[]; metadata: any; } interface TaskManagerProps { onBack: () => void; } export const TaskManager: React.FC = ({ onBack }) => { const [tasks, setTasks] = useState([]); const [selectedTask, setSelectedTask] = useState(0); const [isLoading, setIsLoading] = useState(true); const [showDetails, setShowDetails] = useState(false); const [viewMode, setViewMode] = useState<'all' | 'active' | 'completed' | 'failed'>('active'); // Load task data useEffect(() => { const loadTasks = async () => { try { const persistenceManager = await getPersistenceManager(); const taskData = await persistenceManager.getActiveTasks(); // Transform task data const taskList: Task[] = taskData.map((task: PersistedTask) => ({ id: task.id, type: task.type, description: task.description, status: task.status as any, priority: task.priority, assignedAgent: task.assignedAgent, progress: task.progress, error: task.error, createdAt: new Date(task.createdAt), completedAt: task.completedAt ? new Date(task.completedAt) : undefined, dependencies: task.dependencies ? task.dependencies.split(',').filter(d => d.trim()) : [], metadata: task.metadata ? JSON.parse(task.metadata) : {} })); setTasks(taskList); setIsLoading(false); } catch (error) { console.error('Error loading tasks:', error); setTasks([]); setIsLoading(false); } }; loadTasks(); const interval = setInterval(loadTasks, 2000); // Update every 2 seconds return () => clearInterval(interval); }, [viewMode]); // Handle keyboard input useInput((input, key) => { if (key.escape || input === 'q') { onBack(); } else if (key.upArrow && selectedTask > 0) { setSelectedTask(selectedTask - 1); } else if (key.downArrow && selectedTask < filteredTasks.length - 1) { setSelectedTask(selectedTask + 1); } else if (key.return) { setShowDetails(!showDetails); } else if (input === 'c') { createNewTask(); } else if (input === 'x' && filteredTasks[selectedTask]) { cancelTask(filteredTasks[selectedTask].id); } else if (input === 'r' && filteredTasks[selectedTask]) { retryTask(filteredTasks[selectedTask].id); } else if (input === '1') { setViewMode('all'); } else if (input === '2') { setViewMode('active'); } else if (input === '3') { setViewMode('completed'); } else if (input === '4') { setViewMode('failed'); } }); // Filter tasks based on view mode const filteredTasks = tasks.filter(task => { switch (viewMode) { case 'all': return true; case 'active': return ['pending', 'in_progress'].includes(task.status); case 'completed': return task.status === 'completed'; case 'failed': return ['failed', 'cancelled'].includes(task.status); default: return true; } }); const createNewTask = async () => { try { const persistenceManager = await getPersistenceManager(); const newTask: PersistedTask = { id: `task-${Date.now()}`, type: 'development', description: `New task created at ${new Date().toLocaleTimeString()}`, status: 'pending', priority: 1, dependencies: '', metadata: JSON.stringify({ source: 'ui', automated: false }), progress: 0, createdAt: Date.now() }; await persistenceManager.saveTask(newTask); // Add to local state immediately for responsiveness const task: Task = { id: newTask.id, type: newTask.type, description: newTask.description, status: 'pending', priority: newTask.priority, progress: 0, createdAt: new Date(), dependencies: [], metadata: { source: 'ui', automated: false } }; setTasks(prev => [task, ...prev]); } catch (error) { console.error('Error creating task:', error); } }; const cancelTask = async (taskId: string) => { try { const persistenceManager = await getPersistenceManager(); await persistenceManager.updateTaskStatus(taskId, 'cancelled'); setTasks(prev => prev.map(task => task.id === taskId ? { ...task, status: 'cancelled' as const } : task )); } catch (error) { console.error('Error cancelling task:', error); } }; const retryTask = async (taskId: string) => { try { const persistenceManager = await getPersistenceManager(); await persistenceManager.updateTaskStatus(taskId, 'pending'); setTasks(prev => prev.map(task => task.id === taskId ? { ...task, status: 'pending' as const, error: undefined } : task )); } catch (error) { console.error('Error retrying task:', error); } }; if (isLoading) { return ( Loading task data... ); } return ( {/* Header */} 📋 Task Manager • {filteredTasks.length} tasks • Use ↑↓ to select, Enter for details, 'c' to create, 'q' to quit {/* View Mode Selector */} [1] All ({tasks.length}) [2] Active ({tasks.filter(t => ['pending', 'in_progress'].includes(t.status)).length}) [3] Completed ({tasks.filter(t => t.status === 'completed').length}) [4] Failed ({tasks.filter(t => ['failed', 'cancelled'].includes(t.status)).length}) {/* Task List */} {/* Left Panel - Task List */} Tasks ({viewMode}) {filteredTasks.length === 0 ? ( No tasks found. Press 'c' to create a new task. ) : ( filteredTasks.map((task, index) => ( {task.description.length > 40 ? task.description.slice(0, 37) + '...' : task.description} P{task.priority} {index === selectedTask && ( <> └─ Status: {task.status} {task.assignedAgent && ( └─ Agent: {task.assignedAgent} )} {task.progress > 0 && ( └─ Progress: {task.progress}% )} )} )) )} {/* Right Panel - Task Details */} Task Details {filteredTasks[selectedTask] ? ( ) : ( Select a task to view details )} {/* Footer */} [c] Create Task [x] Cancel Task [r] Retry Task [Enter] Toggle Details [q] Back ); }; // Task Details Component const TaskDetails: React.FC<{ task: Task; showDetails: boolean }> = ({ task, showDetails }) => { const formatDuration = (start: Date, end?: Date) => { const endTime = end || new Date(); const diff = endTime.getTime() - start.getTime(); const minutes = Math.floor(diff / 60000); const hours = Math.floor(minutes / 60); if (hours > 0) return `${hours}h ${minutes % 60}m`; return `${minutes}m`; }; return ( {task.description} ID: {task.id.slice(-12)} Type: {task.type} Status: {task.status.toUpperCase()} Priority: P{task.priority} Progress: {task.progress}% {task.assignedAgent && ( Agent: {task.assignedAgent} )} {task.error && ( Error: {task.error} )} Timeline: Created: {task.createdAt.toLocaleString()} {task.completedAt && ( Completed: {task.completedAt.toLocaleString()} )} Duration: {formatDuration(task.createdAt, task.completedAt)} {showDetails && ( <> Dependencies: {task.dependencies.length > 0 ? ( task.dependencies.map((dep, i) => ( • {dep} )) ) : ( No dependencies )} Metadata: {JSON.stringify(task.metadata, null, 2)} )} ); }; // Helper functions function getStatusColor(status: string): string { switch (status) { case 'pending': return 'yellow'; case 'in_progress': return 'blue'; case 'completed': return 'green'; case 'failed': return 'red'; case 'cancelled': return 'gray'; default: return 'white'; } } function getPriorityColor(priority: number): string { if (priority >= 3) return 'red'; if (priority >= 2) return 'yellow'; return 'green'; } export default TaskManager;