Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
TaskCard.tsx•9.29 kB
import { useDrag, useDrop } from 'react-dnd'; import { useEffect } from 'react'; import { usePlanStore } from '../store/planStore'; import { Task, AgentTemplate, isAgentTask } from '../types/task'; import { GripVertical, Trash2, Clock, Zap, User, Shield } from 'lucide-react'; interface TaskCardProps { task: Task; disableDrag?: boolean; // Disable drag when used in ReorderableTaskCard isExecuting?: boolean; // Disable editing during execution } export function TaskCard({ task, disableDrag = false, isExecuting = false }: TaskCardProps) { const { setSelectedTask, deleteTask, updateTask, agentTemplates, tasks } = usePlanStore(); // Debug: Log when execution status changes useEffect(() => { if (task.executionStatus) { console.log(`🎨 TaskCard ${task.id} (${task.title}) - Status changed to: ${task.executionStatus}`); } }, [task.executionStatus, task.id, task.title]); const [{ isDragging }, drag] = useDrag(() => ({ type: 'task', item: task, canDrag: !disableDrag, // Only allow drag if not disabled collect: (monitor) => ({ isDragging: monitor.isDragging(), }), })); // Find assigned agents (only for agent tasks) const workerAgent = isAgentTask(task) ? agentTemplates.find(a => a.id === task.workerPreambleId) : undefined; const qcAgent = isAgentTask(task) ? agentTemplates.find(a => a.id === task.qcPreambleId) : undefined; // Helper to get task title from task ID const getTaskTitle = (taskId: string): string => { const dependentTask = tasks.find(t => t.id === taskId); return dependentTask?.title || taskId; }; // Drop zone for worker agent const [{ isOverWorker }, dropWorker] = useDrop(() => ({ accept: 'agent', canDrop: (item: AgentTemplate) => item.agentType === 'worker', drop: (item: AgentTemplate) => { updateTask(task.id, { workerPreambleId: item.id, agentRoleDescription: item.role, }); }, collect: (monitor) => ({ isOverWorker: monitor.isOver() && monitor.canDrop(), }), })); // Drop zone for QC agent const [{ isOverQC }, dropQC] = useDrop(() => ({ accept: 'agent', canDrop: (item: AgentTemplate) => item.agentType === 'qc', drop: (item: AgentTemplate) => { updateTask(task.id, { qcPreambleId: item.id, qcRole: item.role, }); }, collect: (monitor) => ({ isOverQC: monitor.isOver() && monitor.canDrop(), }), })); // Get execution status styling const getExecutionStatusClass = () => { if (!task.executionStatus) return 'border-norse-rune hover:border-valhalla-gold'; switch (task.executionStatus) { case 'executing': return 'border-yellow-500 shadow-lg shadow-yellow-500/50 animate-pulse'; case 'completed': return 'border-green-500 shadow-md shadow-green-500/30'; case 'failed': return 'border-red-500 shadow-md shadow-red-500/30'; case 'pending': default: return 'border-gray-600'; } }; return ( <div ref={disableDrag ? undefined : drag} className={`bg-norse-stone border-2 rounded-lg overflow-hidden transition-all ${ isDragging ? 'opacity-50' : '' } ${getExecutionStatusClass()}`} > {/* Header */} <div className="p-4 bg-norse-shadow border-b border-norse-rune"> <div className="flex items-start justify-between mb-2"> <div className="flex items-center space-x-2 flex-1 min-w-0"> {!disableDrag && <GripVertical className="w-4 h-4 text-gray-500 flex-shrink-0 cursor-move" />} <button type="button" onClick={() => !isExecuting && setSelectedTask(task)} disabled={isExecuting} className="font-medium text-gray-100 text-sm truncate hover:text-valhalla-gold transition-colors text-left disabled:cursor-not-allowed disabled:opacity-50" > {task.title} </button> </div> <button type="button" onClick={(e) => { e.stopPropagation(); if (!isExecuting) deleteTask(task.id); }} disabled={isExecuting} className="text-red-400 hover:text-red-600 flex-shrink-0 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" > <Trash2 className="w-4 h-4" /> </button> </div> {isAgentTask(task) && ( <div className="flex items-center justify-between text-xs text-gray-400"> <div className="flex items-center space-x-1"> <Clock className="w-3 h-3" /> <span>{task.estimatedDuration}</span> </div> <div className="flex items-center space-x-1"> <Zap className="w-3 h-3" /> <span>{task.estimatedToolCalls} calls</span> </div> </div> )} </div> {/* Worker Agent Drop Zone */} <div ref={dropWorker} className={`p-3 border-b border-norse-rune transition-all ${ isOverWorker ? 'bg-frost-ice bg-opacity-20 border-frost-ice' : workerAgent ? 'bg-norse-shadow' : 'bg-norse-stone' }`} > <div className="flex items-center space-x-2 mb-2"> <User className="w-4 h-4 text-frost-ice" /> <span className="text-xs font-semibold text-frost-ice uppercase tracking-wide"> Worker Agent </span> </div> {workerAgent ? ( <div className="bg-norse-night rounded p-2 border border-norse-rune"> <div className="flex items-center justify-between"> <div className="flex-1 min-w-0"> <div className="text-sm font-medium text-gray-100 truncate"> {workerAgent.name} </div> <div className="text-xs text-gray-400 line-clamp-1"> {workerAgent.role} </div> </div> <button type="button" onClick={(e) => { e.stopPropagation(); updateTask(task.id, { workerPreambleId: undefined, agentRoleDescription: '', }); }} className="ml-2 text-gray-500 hover:text-red-400 transition-colors" > <Trash2 className="w-3 h-3" /> </button> </div> </div> ) : ( <div className={`border-2 border-dashed rounded p-3 text-center transition-all ${ isOverWorker ? 'border-frost-ice bg-frost-ice bg-opacity-10' : 'border-norse-rune' }`}> <p className="text-xs text-gray-500"> {isOverWorker ? 'Drop worker here' : 'Drag worker agent here'} </p> </div> )} </div> {/* QC Agent Drop Zone */} <div ref={dropQC} className={`p-3 transition-all ${ isOverQC ? 'bg-magic-rune bg-opacity-20 border-t-2 border-magic-rune' : qcAgent ? 'bg-norse-shadow' : 'bg-norse-stone' }`} > <div className="flex items-center space-x-2 mb-2"> <Shield className="w-4 h-4 text-magic-rune" /> <span className="text-xs font-semibold text-magic-rune uppercase tracking-wide"> QC Agent </span> </div> {qcAgent ? ( <div className="bg-norse-night rounded p-2 border border-norse-rune"> <div className="flex items-center justify-between"> <div className="flex-1 min-w-0"> <div className="text-sm font-medium text-gray-100 truncate"> {qcAgent.name} </div> <div className="text-xs text-gray-400 line-clamp-1"> {qcAgent.role} </div> </div> <button type="button" onClick={(e) => { e.stopPropagation(); updateTask(task.id, { qcPreambleId: undefined, qcRole: '', }); }} className="ml-2 text-gray-500 hover:text-red-400 transition-colors" > <Trash2 className="w-3 h-3" /> </button> </div> </div> ) : ( <div className={`border-2 border-dashed rounded p-3 text-center transition-all ${ isOverQC ? 'border-magic-rune bg-magic-rune bg-opacity-10' : 'border-norse-rune' }`}> <p className="text-xs text-gray-500"> {isOverQC ? 'Drop QC agent here' : 'Drag QC agent here'} </p> </div> )} </div> {/* Dependencies Footer */} {task.dependencies.length > 0 && ( <div className="px-3 py-2 bg-norse-shadow border-t border-norse-rune"> <p className="text-xs text-gray-500"> Depends on: {task.dependencies.map(getTaskTitle).join(', ')} </p> </div> )} </div> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server