import { useEffect, useState, useCallback, useMemo } from 'react' import { useNavigate } from 'react-router-dom' import { BookOpen, Plus, Trash2, Play, Search, Tag, Zap, X, Save, RefreshCw, Layers, Star, PenTool, Inbox, AlertTriangle } from 'lucide-react' import Card from '../components/common/Card' import Button from '../components/common/Button' import Input from '../components/common/Input' import Textarea from '../components/common/Textarea' import { agentApi } from '../services/api' import type { AgentTask } from '../types' /* ─── Constants ────────────────────────────────────────────────── */ const CATEGORIES = [ { id: 'all', name: 'All Tasks', color: 'dark' }, { id: 'full_auto', name: 'Full Auto', color: 'primary' }, { id: 'recon', name: 'Reconnaissance', color: 'blue' }, { id: 'vulnerability', name: 'Vulnerability', color: 'orange' }, { id: 'custom', name: 'Custom', color: 'purple' }, { id: 'reporting', name: 'Reporting', color: 'green' } ] /* ─── Toast System ─────────────────────────────────────────────── */ interface Toast { id: number; message: string; severity: 'info' | 'success' | 'warning' | 'error' } let _toastId = 0 function ToastContainer({ toasts, onDismiss }: { toasts: Toast[]; onDismiss: (id: number) => void }) { if (toasts.length === 0) return null const border: Record = { info: 'border-blue-500', success: 'border-green-500', warning: 'border-yellow-500', error: 'border-red-500', } return (
{toasts.map(t => (
{t.message}
))}
) } /* ─── Main Component ───────────────────────────────────────────── */ export default function TaskLibraryPage() { const navigate = useNavigate() const [tasks, setTasks] = useState([]) const [loading, setLoading] = useState(true) const [refreshing, setRefreshing] = useState(false) const [selectedCategory, setSelectedCategory] = useState('all') const [searchQuery, setSearchQuery] = useState('') const [selectedTask, setSelectedTask] = useState(null) const [toasts, setToasts] = useState([]) // Create task modal const [showCreateModal, setShowCreateModal] = useState(false) const [newTask, setNewTask] = useState({ name: '', description: '', category: 'custom', prompt: '', system_prompt: '', tags: '' }) const [creating, setCreating] = useState(false) const [deleteConfirm, setDeleteConfirm] = useState(null) /* ── Toast helpers ──────────────────────────────────────────── */ const addToast = useCallback((message: string, severity: Toast['severity'] = 'info') => { const id = ++_toastId setToasts(prev => [...prev.slice(-4), { id, message, severity }]) setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 5000) }, []) const dismissToast = useCallback((id: number) => { setToasts(prev => prev.filter(t => t.id !== id)) }, []) /* ── Data fetch ─────────────────────────────────────────────── */ const loadTasks = useCallback(async () => { try { const taskList = await agentApi.tasks.list() setTasks(taskList) } catch (error) { console.error('Failed to load tasks:', error) } }, []) useEffect(() => { setLoading(true) loadTasks().finally(() => setLoading(false)) }, [loadTasks]) const handleRefresh = useCallback(async () => { setRefreshing(true) await loadTasks() setRefreshing(false) }, [loadTasks]) /* ── Derived data ───────────────────────────────────────────── */ const filteredTasks = useMemo(() => { let filtered = [...tasks] // Category filter if (selectedCategory !== 'all') { filtered = filtered.filter(t => t.category === selectedCategory) } // Search filter if (searchQuery.trim()) { const query = searchQuery.toLowerCase() filtered = filtered.filter(t => t.name.toLowerCase().includes(query) || t.description.toLowerCase().includes(query) || t.tags?.some(tag => tag.toLowerCase().includes(query)) ) } return filtered }, [tasks, selectedCategory, searchQuery]) const stats = useMemo(() => { const presetCount = tasks.filter(t => t.is_preset).length const customCount = tasks.filter(t => !t.is_preset).length const categoryCounts: Record = {} CATEGORIES.filter(c => c.id !== 'all').forEach(cat => { categoryCounts[cat.id] = tasks.filter(t => t.category === cat.id).length }) return { total: tasks.length, presetCount, customCount, categoryCounts } }, [tasks]) /* ── Handlers ───────────────────────────────────────────────── */ const handleCreateTask = useCallback(async () => { if (!newTask.name.trim() || !newTask.prompt.trim()) return setCreating(true) try { await agentApi.tasks.create({ name: newTask.name, description: newTask.description, category: newTask.category, prompt: newTask.prompt, system_prompt: newTask.system_prompt || undefined, tags: newTask.tags.split(',').map(t => t.trim()).filter(t => t) }) // Reload tasks await loadTasks() setShowCreateModal(false) setNewTask({ name: '', description: '', category: 'custom', prompt: '', system_prompt: '', tags: '' }) addToast('Task created successfully', 'success') } catch (error) { console.error('Failed to create task:', error) addToast('Failed to create task', 'error') } finally { setCreating(false) } }, [newTask, loadTasks, addToast]) const handleDeleteTask = useCallback(async (taskId: string) => { try { await agentApi.tasks.delete(taskId) await loadTasks() setDeleteConfirm(null) if (selectedTask?.id === taskId) { setSelectedTask(null) } addToast('Task deleted', 'success') } catch (error) { console.error('Failed to delete task:', error) addToast('Failed to delete task', 'error') } }, [loadTasks, selectedTask, addToast]) const handleRunTask = useCallback((task: AgentTask) => { // Navigate to new scan page with task pre-selected navigate('/scan/new', { state: { selectedTaskId: task.id } }) }, [navigate]) const handleSelectTask = useCallback((task: AgentTask) => { setSelectedTask(task) }, []) const handleClearSearch = useCallback(() => { setSearchQuery('') }, []) /* ── Loading state ──────────────────────────────────────────── */ if (loading) { return (
) } /* ── Render ─────────────────────────────────────────────────── */ return (
{/* Header */}

Task Library

Manage and create reusable security testing tasks

{/* Stats Row */} {tasks.length > 0 && (
{/* Total Tasks */}

{stats.total}

Total Tasks

{/* Presets */}

{stats.presetCount}

Presets

{/* Custom */}

{stats.customCount}

Custom

{/* Category Breakdown */}
{CATEGORIES.filter(c => c.id !== 'all' && stats.categoryCounts[c.id] > 0).map(cat => { const colorMap: Record = { primary: 'text-primary-400 bg-primary-500/10', blue: 'text-blue-400 bg-blue-500/10', orange: 'text-orange-400 bg-orange-500/10', purple: 'text-purple-400 bg-purple-500/10', green: 'text-green-400 bg-green-500/10', } const cls = colorMap[cat.color] || 'text-dark-300 bg-dark-700' return (
{cat.id} {stats.categoryCounts[cat.id]}
) })}
)} {/* Filters */}
{/* Search */}
setSearchQuery(e.target.value)} className="w-full pl-10 pr-9 py-2 bg-dark-900 border border-dark-700 rounded-lg text-white placeholder-dark-500 focus:border-primary-500 focus:outline-none" /> {searchQuery && ( )}
{/* Category Filter */}
{CATEGORIES.map((cat) => ( ))}
{/* Task List */}
{filteredTasks.length === 0 ? (
{searchQuery || selectedCategory !== 'all' ? ( <>

No Tasks Match

No tasks found matching your current filters.

) : ( <>

No Tasks Yet

Create your first reusable security testing task.

)}
) : (
{filteredTasks.map((task, idx) => (
handleSelectTask(task)} className={`bg-dark-800 rounded-lg border p-4 cursor-pointer transition-all ${ selectedTask?.id === task.id ? 'border-primary-500 bg-primary-500/5' : 'border-dark-700 hover:border-dark-500' }`} style={{ animation: `fadeSlideIn 0.3s ease-out ${Math.min(idx * 0.04, 0.4)}s both` }} >
{task.name} {task.is_preset && ( Preset )}

{task.description}

{task.category} {task.estimated_tokens > 0 && ( ~{task.estimated_tokens} tokens )}
{task.tags?.length > 0 && (
{task.tags.slice(0, 5).map((tag) => ( {tag} ))} {task.tags.length > 5 && ( +{task.tags.length - 5} more )}
)}
{!task.is_preset && ( )}
))}
)}
{/* Task Details */}
{selectedTask ? (

Name

{selectedTask.name}

Description

{selectedTask.description}

Category

{selectedTask.category}

Prompt

                    {selectedTask.prompt}
                  
{selectedTask.system_prompt && (

System Prompt

                      {selectedTask.system_prompt}
                    
)} {selectedTask.tools_required?.length > 0 && (

Required Tools

{selectedTask.tools_required.map((tool) => ( {tool} ))}
)}
) : (

Select a task to view details

)}
{/* Create Task Modal */} {showCreateModal && (
setShowCreateModal(false)}>
e.stopPropagation()} style={{ animation: 'fadeSlideIn 0.2s ease-out' }} >

Create New Task

setNewTask({ ...newTask, name: e.target.value })} /> setNewTask({ ...newTask, description: e.target.value })} />