Skip to main content
Glama
orneryd

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

by orneryd
TaskEditor.tsx•25.8 kB
import { usePlanStore } from '../store/planStore'; import { X, Plus, Trash2 } from 'lucide-react'; import { useState, useEffect } from 'react'; import { Task, isAgentTask, isTransformerTask, AgentTask, TransformerTask } from '../types/task'; export function TaskEditor() { const { selectedTask, updateTask, setSelectedTask, tasks, agentTemplates, lambdas, parallelGroups, addParallelGroup } = usePlanStore(); const [localTask, setLocalTask] = useState<Task | null>(selectedTask); const [isCreatingGroup, setIsCreatingGroup] = useState(false); const [newGroupName, setNewGroupName] = useState(''); useEffect(() => { setLocalTask(selectedTask); }, [selectedTask]); if (!selectedTask || !localTask) { return ( <div className="p-6 text-center text-gray-400"> <div className="text-6xl mb-4">📝</div> <p className="text-lg font-medium">Select a task to edit its details</p> </div> ); } const handleSave = () => { updateTask(selectedTask.id, localTask); setSelectedTask(null); }; // For transformer tasks, show a simplified editor if (isTransformerTask(localTask)) { return ( <div className="p-4 space-y-4"> <div className="flex items-center justify-between border-b border-norse-rune pb-4"> <h2 className="text-lg font-semibold text-violet-400">Transformer Editor</h2> <button type="button" onClick={() => setSelectedTask(null)} className="text-gray-400 hover:text-violet-400 transition-colors" > <X className="w-5 h-5" /> </button> </div> <div className="space-y-4"> <div> <label className="block text-xs font-medium text-gray-300 mb-1">Title</label> <input type="text" value={localTask.title} onChange={(e) => updateTransformerTask({ title: e.target.value })} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-violet-500 focus:border-violet-500 text-sm text-gray-100" /> </div> <div> <label className="block text-xs font-medium text-gray-300 mb-1">Description</label> <textarea value={localTask.description || ''} onChange={(e) => updateTransformerTask({ description: e.target.value })} rows={3} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-violet-500 focus:border-violet-500 text-sm text-gray-100" placeholder="What does this transformer do?" /> </div> <div> <label className="block text-xs font-medium text-gray-300 mb-1">Lambda Language</label> <select value={localTask.lambdaLanguage || 'javascript'} onChange={(e) => updateTransformerTask({ lambdaLanguage: e.target.value as 'javascript' | 'typescript' | 'python' })} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-violet-500 focus:border-violet-500 text-sm text-gray-100" > <option value="javascript">JavaScript</option> <option value="typescript">TypeScript</option> <option value="python">Python</option> </select> </div> <div> <label className="block text-xs font-medium text-gray-300 mb-1">Lambda Name</label> <input type="text" value={localTask.lambdaName || ''} onChange={(e) => updateTransformerTask({ lambdaName: e.target.value || undefined })} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-violet-500 focus:border-violet-500 text-sm text-gray-100" placeholder="my-transformer-lambda" /> <p className="text-xs text-gray-400 mt-1"> Identifier for logging (e.g., split-batch-1) </p> </div> <div> <label className="block text-xs font-medium text-gray-300 mb-1">Lambda Script</label> <textarea value={localTask.lambdaScript || ''} onChange={(e) => updateTransformerTask({ lambdaScript: e.target.value || undefined })} rows={10} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-violet-500 focus:border-violet-500 text-sm text-gray-100 font-mono" placeholder={`// Access upstream outputs via tasks array:\nconst output = tasks[0].transformerOutput || tasks[0].workerOutput || '';\nconst data = JSON.parse(output);\nreturn JSON.stringify(data);`} spellCheck={false} /> <p className="text-xs text-gray-400 mt-1"> Inline script code. Use <code className="text-violet-400">tasks[0].transformerOutput</code> or <code className="text-violet-400">tasks[0].workerOutput</code> to access upstream data. </p> </div> <details className="text-xs"> <summary className="text-gray-400 cursor-pointer hover:text-gray-300">Or select from library...</summary> <div className="mt-2"> <select value={localTask.lambdaId || ''} onChange={(e) => { const selectedLambda = lambdas.find(l => l.id === e.target.value); if (selectedLambda) { updateTransformerTask({ lambdaId: selectedLambda.id, lambdaScript: selectedLambda.script, lambdaLanguage: selectedLambda.language, lambdaName: selectedLambda.name }); } else { updateTransformerTask({ lambdaId: undefined }); } }} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-violet-500 focus:border-violet-500 text-sm text-gray-100" > <option value="">None (use inline script above)</option> {lambdas.map(lambda => ( <option key={lambda.id} value={lambda.id}> {lambda.name} ({lambda.language}) </option> ))} </select> </div> </details> {/* Parallel Group for Transformer */} <div> <div className="flex items-center justify-between mb-1"> <label className="block text-xs font-medium text-gray-300">Parallel Group</label> <button type="button" onClick={() => setIsCreatingGroup(!isCreatingGroup)} className="text-xs text-violet-400 hover:text-violet-300 flex items-center space-x-1" > <Plus className="w-3 h-3" /> <span>{isCreatingGroup ? 'Cancel' : 'New Group'}</span> </button> </div> {isCreatingGroup ? ( <div className="space-y-2"> <input type="text" value={newGroupName} onChange={(e) => setNewGroupName(e.target.value)} placeholder="Group name (optional)" className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-violet-500 focus:border-violet-500 text-sm text-gray-100" /> <button type="button" onClick={() => { addParallelGroup(); const newGroupId = Math.max(0, ...parallelGroups.map(g => g.id)) + 1; // Update both local state and store immediately updateTransformerTask({ parallelGroup: newGroupId }); updateTask(selectedTask.id, { parallelGroup: newGroupId }); setIsCreatingGroup(false); setNewGroupName(''); }} className="w-full px-3 py-2 bg-violet-600 text-white rounded-lg hover:bg-violet-500 text-sm font-medium" > Create & Assign Group {Math.max(0, ...parallelGroups.map(g => g.id)) + 1} </button> </div> ) : ( <select value={localTask.parallelGroup ?? ''} onChange={(e) => { const value = e.target.value; const newGroup = value === '' ? null : parseInt(value, 10); // Update both local state and store immediately updateTransformerTask({ parallelGroup: newGroup }); updateTask(selectedTask.id, { parallelGroup: newGroup }); }} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-violet-500 focus:border-violet-500 text-sm text-gray-100" > <option value="">None (ungrouped)</option> {parallelGroups.map((group) => ( <option key={group.id} value={group.id}> Group {group.id}{group.name ? ` - ${group.name}` : ''} </option> ))} </select> )} <p className="text-xs text-gray-400 mt-1"> Tasks in the same group can run in parallel </p> </div> </div> <div className="flex justify-end space-x-2 pt-4 border-t border-norse-rune"> <button type="button" onClick={() => setSelectedTask(null)} className="px-4 py-2 bg-norse-rune text-gray-200 rounded-lg hover:bg-norse-stone transition-colors" > Cancel </button> <button type="button" onClick={handleSave} className="px-4 py-2 bg-violet-600 text-white rounded-lg hover:bg-violet-500 transition-colors" > Save Changes </button> </div> </div> ); } // Cast to AgentTask since we've ruled out TransformerTask above const agentTask = localTask as AgentTask; // Type-safe update for agent tasks const updateAgentTask = (updates: Partial<AgentTask>) => { if (localTask && isAgentTask(localTask)) { const updated: AgentTask = Object.assign({}, localTask, updates); setLocalTask(updated); } }; // Type-safe update for transformer tasks const updateTransformerTask = (updates: Partial<TransformerTask>) => { if (localTask && isTransformerTask(localTask)) { const updated: TransformerTask = Object.assign({}, localTask, updates); setLocalTask(updated); } }; const addSuccessCriterion = () => { if (localTask && isAgentTask(localTask)) { const updated: AgentTask = Object.assign({}, localTask, { successCriteria: [...localTask.successCriteria, ''], }); setLocalTask(updated); } }; const updateSuccessCriterion = (index: number, value: string) => { if (localTask && isAgentTask(localTask)) { const newCriteria = [...localTask.successCriteria]; newCriteria[index] = value; const updated: AgentTask = Object.assign({}, localTask, { successCriteria: newCriteria }); setLocalTask(updated); } }; const removeSuccessCriterion = (index: number) => { if (localTask && isAgentTask(localTask)) { const updated: AgentTask = Object.assign({}, localTask, { successCriteria: localTask.successCriteria.filter((_: string, i: number) => i !== index), }); setLocalTask(updated); } }; const availableDependencies = tasks .filter((t) => t.id !== selectedTask.id) .map((t) => t.id); return ( <div className="p-6 space-y-6"> <div className="flex items-center justify-between"> <h2 className="text-lg font-semibold text-valhalla-gold">Task Editor</h2> <button type="button" onClick={() => setSelectedTask(null)} className="text-gray-400 hover:text-valhalla-gold transition-colors" > <X className="w-5 h-5" /> </button> </div> <div className="space-y-4"> {/* Task ID */} <div> <label className="block text-xs font-medium text-gray-300 mb-1"> Task ID </label> <input type="text" value={agentTask.id} onChange={(e) => updateAgentTask({ id: e.target.value })} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-valhalla-gold focus:border-valhalla-gold text-sm text-gray-100" /> </div> {/* Title */} <div> <label className="block text-xs font-medium text-gray-300 mb-1"> Title </label> <input type="text" value={agentTask.title} onChange={(e) => updateAgentTask({ title: e.target.value })} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-valhalla-gold focus:border-valhalla-gold text-sm text-gray-100" /> </div> {/* Agent Role Description */} <div> <label className="block text-xs font-medium text-gray-300 mb-1"> Worker Role Description </label> <textarea value={agentTask.agentRoleDescription} onChange={(e) => updateAgentTask({ agentRoleDescription: e.target.value })} rows={3} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-valhalla-gold focus:border-valhalla-gold text-sm text-gray-100" /> </div> {/* Worker Preamble Selection */} <div> <label className="block text-xs font-medium text-gray-300 mb-1"> Worker Preamble (Optional) </label> <select value={agentTask.workerPreambleId || ''} onChange={(e) => updateAgentTask({ workerPreambleId: e.target.value || undefined })} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-valhalla-gold focus:border-valhalla-gold text-sm text-gray-100" > <option value="">None - Use role description only</option> {agentTemplates .filter(a => a.agentType === 'worker') .map(agent => ( <option key={agent.id} value={agent.id}> {agent.name} (v{agent.version}) </option> ))} </select> <p className="text-xs text-gray-400 mt-1"> Link to a saved worker preamble for consistent role execution </p> </div> {/* QC Role Description */} <div> <label className="block text-xs font-medium text-gray-300 mb-1"> QC Role Description </label> <textarea value={agentTask.qcRole} onChange={(e) => updateAgentTask({ qcRole: e.target.value })} rows={2} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-valhalla-gold focus:border-valhalla-gold text-sm text-gray-100" /> </div> {/* QC Preamble Selection */} <div> <label className="block text-xs font-medium text-gray-300 mb-1"> QC Preamble (Optional) </label> <select value={agentTask.qcPreambleId || ''} onChange={(e) => updateAgentTask({ qcPreambleId: e.target.value || undefined })} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-valhalla-gold focus:border-valhalla-gold text-sm text-gray-100" > <option value="">None - Use role description only</option> {agentTemplates .filter(a => a.agentType === 'qc') .map(agent => ( <option key={agent.id} value={agent.id}> {agent.name} (v{agent.version}) </option> ))} </select> <p className="text-xs text-gray-400 mt-1"> Link to a saved QC preamble for consistent verification </p> </div> {/* Recommended Model */} <div> <label className="block text-xs font-medium text-gray-300 mb-1"> Recommended Model </label> <select value={agentTask.recommendedModel} onChange={(e) => updateAgentTask({ recommendedModel: e.target.value })} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-valhalla-gold focus:border-valhalla-gold text-sm text-gray-100" > <option value="gpt-4.1">gpt-4.1</option> <option value="gpt-4">gpt-4</option> <option value="claude-3.5-sonnet">claude-3.5-sonnet</option> <option value="claude-3-opus">claude-3-opus</option> </select> </div> {/* Prompt */} <div> <label className="block text-xs font-medium text-gray-300 mb-1"> Prompt </label> <textarea value={agentTask.prompt} onChange={(e) => updateAgentTask({ prompt: e.target.value })} rows={6} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-valhalla-gold focus:border-valhalla-gold text-sm font-mono text-gray-100" placeholder="Enter the detailed prompt for this task..." /> </div> {/* Success Criteria */} <div> <div className="flex items-center justify-between mb-2"> <label className="block text-xs font-medium text-gray-300"> Success Criteria </label> <button type="button" onClick={addSuccessCriterion} className="text-valhalla-gold hover:text-valhalla-amber text-xs flex items-center space-x-1 transition-colors" > <Plus className="w-3 h-3" /> <span>Add</span> </button> </div> <div className="space-y-2"> {agentTask.successCriteria.map((criterion, index) => ( <div key={index} className="flex items-start space-x-2"> <input type="text" value={criterion} onChange={(e) => updateSuccessCriterion(index, e.target.value)} placeholder="Enter success criterion..." className="flex-1 px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-valhalla-gold focus:border-valhalla-gold text-sm text-gray-100" /> <button type="button" onClick={() => removeSuccessCriterion(index)} className="text-red-400 hover:text-red-600 p-2 transition-colors" > <Trash2 className="w-4 h-4" /> </button> </div> ))} </div> </div> {/* Dependencies */} <div> <label className="block text-xs font-medium text-gray-300 mb-1"> Dependencies </label> <select multiple value={agentTask.dependencies} onChange={(e) => { const selected = Array.from(e.target.selectedOptions, (option) => option.value); updateAgentTask({ dependencies: selected }); }} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-valhalla-gold focus:border-valhalla-gold text-sm text-gray-100" size={5} > {availableDependencies.map((taskId) => ( <option key={taskId} value={taskId}> {taskId} </option> ))} </select> <p className="text-xs text-gray-400 mt-1"> Hold Cmd/Ctrl to select multiple </p> </div> {/* Parallel Group */} <div> <div className="flex items-center justify-between mb-1"> <label className="block text-xs font-medium text-gray-300"> Parallel Group </label> <button type="button" onClick={() => setIsCreatingGroup(!isCreatingGroup)} className="text-xs text-frost-ice hover:text-frost-ice/80 flex items-center space-x-1" > <Plus className="w-3 h-3" /> <span>{isCreatingGroup ? 'Cancel' : 'New Group'}</span> </button> </div> {isCreatingGroup ? ( <div className="space-y-2"> <input type="text" value={newGroupName} onChange={(e) => setNewGroupName(e.target.value)} placeholder="Group name (optional)" className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-frost-ice focus:border-frost-ice text-sm text-gray-100" /> <button type="button" onClick={() => { addParallelGroup(); // Get the newly created group (it's the last one) const newGroupId = Math.max(0, ...parallelGroups.map(g => g.id)) + 1; // Update both local state and store immediately updateAgentTask({ parallelGroup: newGroupId }); updateTask(selectedTask.id, { parallelGroup: newGroupId }); setIsCreatingGroup(false); setNewGroupName(''); }} className="w-full px-3 py-2 bg-frost-ice text-norse-night rounded-lg hover:bg-frost-ice/90 text-sm font-medium" > Create & Assign Group {Math.max(0, ...parallelGroups.map(g => g.id)) + 1} </button> </div> ) : ( <select value={agentTask.parallelGroup ?? ''} onChange={(e) => { const value = e.target.value; const newGroup = value === '' ? null : parseInt(value, 10); // Update both local state and store immediately updateAgentTask({ parallelGroup: newGroup }); updateTask(selectedTask.id, { parallelGroup: newGroup }); }} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-valhalla-gold focus:border-valhalla-gold text-sm text-gray-100" > <option value="">None (ungrouped)</option> {parallelGroups.map((group) => ( <option key={group.id} value={group.id}> Group {group.id}{group.name ? ` - ${group.name}` : ''} </option> ))} </select> )} <p className="text-xs text-gray-400 mt-1"> Tasks in the same group can run in parallel (List view concept) </p> </div> {/* Estimated Duration */} <div> <label className="block text-xs font-medium text-gray-300 mb-1"> Estimated Duration </label> <input type="text" value={agentTask.estimatedDuration} onChange={(e) => updateAgentTask({ estimatedDuration: e.target.value })} placeholder="e.g., 30 minutes" className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-valhalla-gold focus:border-valhalla-gold text-sm text-gray-100" /> </div> {/* Estimated Tool Calls */} <div> <label className="block text-xs font-medium text-gray-300 mb-1"> Estimated Tool Calls </label> <input type="number" value={agentTask.estimatedToolCalls} onChange={(e) => updateAgentTask({ estimatedToolCalls: parseInt(e.target.value) || 0 })} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-valhalla-gold focus:border-valhalla-gold text-sm text-gray-100" /> </div> {/* Max Retries */} <div> <label className="block text-xs font-medium text-gray-300 mb-1"> Max Retries </label> <input type="number" value={agentTask.maxRetries} onChange={(e) => updateAgentTask({ maxRetries: parseInt(e.target.value) || 0 })} className="w-full px-3 py-2 bg-norse-shadow border-2 border-norse-rune rounded-lg focus:ring-2 focus:ring-valhalla-gold focus:border-valhalla-gold text-sm text-gray-100" /> </div> {/* Save Button */} <button type="button" onClick={handleSave} className="w-full px-4 py-2 bg-valhalla-gold text-norse-night rounded-lg hover:bg-valhalla-amber transition-all font-semibold shadow-lg hover:shadow-valhalla-gold/30" > Save Changes </button> </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