Skip to main content
Glama
orneryd

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

by orneryd
AgentPalette.tsx11.4 kB
import { useState, useEffect, useRef } from 'react'; import { useDrag } from 'react-dnd'; import { usePlanStore } from '../store/planStore'; import { AgentTemplate } from '../types/task'; import { GripVertical, Plus, Search, Eye, Trash2, ChevronDown, ChevronRight } from 'lucide-react'; import { CreateModal } from './CreateModal'; import { AgentDetailsModal } from './AgentDetailsModal'; interface DraggableAgentProps { agent: AgentTemplate; isOperating: boolean; onViewDetails: (agent: AgentTemplate) => void; onDelete: (agentId: string) => void; } function DraggableAgent({ agent, isOperating, onViewDetails, onDelete }: DraggableAgentProps) { const [{ isDragging }, drag, preview] = useDrag(() => ({ type: 'agent', item: agent, collect: (monitor) => ({ isDragging: monitor.isDragging(), }), }), [agent]); const isDefault = agent.id.startsWith('default-'); // Hide default preview, we'll show custom preview in TaskCanvas preview(null as any, { captureDraggingState: true }); return ( <div ref={drag} className={`p-4 bg-norse-stone border-2 border-norse-rune rounded-lg hover:border-valhalla-gold hover:shadow-lg hover:shadow-valhalla-gold/20 transition-all ${ isDragging ? 'opacity-50' : '' } ${isOperating ? 'opacity-50 pointer-events-none' : 'cursor-move'}`} > <div className="flex items-start space-x-3"> <GripVertical className="w-4 h-4 text-gray-500 flex-shrink-0 mt-1" /> <div className="flex-1 min-w-0"> <div className="flex items-center justify-between mb-1"> <h3 className="font-semibold text-gray-100 text-sm">{agent.name}</h3> <div className="flex items-center gap-2"> <span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${ agent.agentType === 'qc' ? 'bg-magic-rune text-gray-100' : 'bg-frost-ice text-norse-night' }`}> {agent.agentType === 'qc' ? 'QC' : 'Worker'} </span> <button type="button" onClick={(e) => { e.stopPropagation(); onViewDetails(agent); }} className="p-1 text-gray-400 hover:text-frost-ice transition-colors" title="View details" > <Eye className="w-4 h-4" /> </button> {!isDefault && ( <button type="button" onClick={(e) => { e.stopPropagation(); onDelete(agent.id); }} disabled={isOperating} className="p-1 text-gray-400 hover:text-red-400 transition-colors disabled:opacity-50" title="Delete agent" > <Trash2 className="w-4 h-4" /> </button> )} </div> </div> <p className="text-xs text-gray-400 line-clamp-2"> {agent.role} </p> <div className="mt-2 text-xs text-gray-500"> v{agent.version} {isOperating && <span className="ml-2 text-valhalla-gold">Processing...</span>} </div> </div> </div> </div> ); } export function AgentPalette() { const { agentTemplates, agentSearch, hasMoreAgents, isLoadingAgents, isCreatingAgent, fetchAgents, setAgentSearch, deleteAgent, selectedAgent, setSelectedAgent, agentOperations, } = usePlanStore(); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [searchInput, setSearchInput] = useState(''); const [workersCollapsed, setWorkersCollapsed] = useState(true); // Collapsed by default const [qcCollapsed, setQcCollapsed] = useState(true); // Collapsed by default const scrollContainerRef = useRef<HTMLDivElement>(null); const observerTarget = useRef<HTMLDivElement>(null); const handleViewDetails = (agent: AgentTemplate) => { setSelectedAgent(agent); }; const handleDelete = async (agentId: string) => { try { await deleteAgent(agentId); } catch (error) { console.error('Failed to delete agent:', error); } }; // Initial load useEffect(() => { fetchAgents(undefined, true); }, [fetchAgents]); // Search with debounce useEffect(() => { const timer = setTimeout(() => { if (searchInput !== agentSearch) { setAgentSearch(searchInput); fetchAgents(searchInput, true); } }, 300); return () => clearTimeout(timer); }, [searchInput, agentSearch, setAgentSearch, fetchAgents]); // Infinite scroll useEffect(() => { const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && hasMoreAgents && !isLoadingAgents) { fetchAgents(); } }, { threshold: 0.1 } ); const currentTarget = observerTarget.current; if (currentTarget) { observer.observe(currentTarget); } return () => { if (currentTarget) { observer.unobserve(currentTarget); } }; }, [hasMoreAgents, isLoadingAgents, fetchAgents]); const workerAgents = agentTemplates.filter(a => a.agentType === 'worker'); const qcAgents = agentTemplates.filter(a => a.agentType === 'qc'); return ( <> <div className="flex flex-col"> {/* Loading indicator bar */} {isLoadingAgents && ( <div className="h-1 bg-norse-stone overflow-hidden"> <div className="h-full bg-gradient-to-r from-valhalla-gold via-valhalla-amber to-valhalla-gold animate-shimmer bg-[length:200%_100%]" /> </div> )} {/* Header with search and create button */} <div className="p-4 border-b border-gray-200 space-y-3"> <div className="flex items-center justify-between"> <div> <h2 className="text-lg font-bold text-valhalla-gold">Agent Library</h2> <p className="text-xs text-gray-400 mt-0.5"> {agentTemplates.length} agents {isLoadingAgents && '(loading...)'} </p> </div> <button type="button" onClick={() => setIsCreateModalOpen(true)} disabled={isCreatingAgent} className="p-2 bg-valhalla-gold text-norse-night rounded-lg hover:bg-valhalla-amber transition-colors shadow-lg disabled:opacity-50 disabled:cursor-not-allowed relative" title={isCreatingAgent ? "Creating agent..." : "Create new agent"} > {isCreatingAgent ? ( <div className="animate-spin h-5 w-5 border-2 border-norse-night border-t-transparent rounded-full" /> ) : ( <Plus className="w-5 h-5 font-bold" /> )} </button> </div> {/* Search bar */} <div className="relative"> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-500" /> <input type="text" value={searchInput} onChange={(e) => setSearchInput(e.target.value)} placeholder="Search agents..." className="w-full pl-9 pr-3 py-2 text-sm bg-norse-stone border-2 border-norse-rune text-gray-100 placeholder-gray-500 rounded-lg focus:ring-2 focus:ring-valhalla-gold focus:border-valhalla-gold" /> </div> </div> {/* Scrollable agent list */} <div ref={scrollContainerRef} className="p-4 space-y-4"> {/* Worker Agents - Collapsible */} {workerAgents.length > 0 && ( <div className="space-y-2"> <button type="button" onClick={() => setWorkersCollapsed(!workersCollapsed)} className="w-full flex items-center justify-between text-xs font-bold text-frost-ice uppercase tracking-wide hover:text-frost-ice/80 transition-colors py-1" > <span className="flex items-center space-x-2"> {workersCollapsed ? ( <ChevronRight className="w-4 h-4" /> ) : ( <ChevronDown className="w-4 h-4" /> )} <span>Worker Agents ({workerAgents.length})</span> </span> </button> {!workersCollapsed && ( <div className="space-y-2"> {workerAgents.map((agent) => ( <DraggableAgent key={agent.id} agent={agent} isOperating={!!agentOperations[agent.id]} onViewDetails={handleViewDetails} onDelete={handleDelete} /> ))} </div> )} </div> )} {/* QC Agents - Collapsible */} {qcAgents.length > 0 && ( <div className="space-y-2"> <button type="button" onClick={() => setQcCollapsed(!qcCollapsed)} className="w-full flex items-center justify-between text-xs font-bold text-magic-spell uppercase tracking-wide hover:text-magic-spell/80 transition-colors py-1" > <span className="flex items-center space-x-2"> {qcCollapsed ? ( <ChevronRight className="w-4 h-4" /> ) : ( <ChevronDown className="w-4 h-4" /> )} <span>QC Agents ({qcAgents.length})</span> </span> </button> {!qcCollapsed && ( <div className="space-y-2"> {qcAgents.map((agent) => ( <DraggableAgent key={agent.id} agent={agent} isOperating={!!agentOperations[agent.id]} onViewDetails={handleViewDetails} onDelete={handleDelete} /> ))} </div> )} </div> )} {isLoadingAgents && agentTemplates.length > 0 && ( <div className="flex items-center justify-center py-4"> <div className="animate-spin h-5 w-5 border-2 border-valhalla-gold border-t-transparent rounded-full" /> </div> )} {!isLoadingAgents && agentTemplates.length === 0 && ( <div className="text-center py-12 text-gray-400"> <p className="text-base font-medium mb-2">No agents found</p> <p className="text-sm">Try adjusting your search or create a new agent</p> </div> )} {/* Infinite scroll trigger */} <div ref={observerTarget} className="h-4" /> </div> </div> <CreateModal isOpen={isCreateModalOpen} onClose={() => setIsCreateModalOpen(false)} initialTab="agent" /> <AgentDetailsModal agent={selectedAgent} onClose={() => setSelectedAgent(null)} /> </> ); }

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