| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- "use client"
- import type { Agent } from "@/types/agent"
- import { AgentCard, AgentCardHandle } from "./agent-card"
- import { Input } from "@/components/ui/input"
- import { Button } from "@/components/ui/button"
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
- import { Brain, Code, Microscope, Users, Shield, TrendingUp, Bitcoin, Briefcase, ClipboardList, RefreshCcw } from "lucide-react"
- import { motion } from "framer-motion"
- import { useMemo, useRef, useState } from "react"
- import Link from "next/link"
- interface AgentGridProps {
- agents: Agent[]
- onUpdateAgent?: (agent: Agent) => void
- }
- const categories = [
- { name: "All Agents", icon: Brain, key: "all" },
- { name: "Programmer", icon: Code, key: "programmer" },
- { name: "Researcher", icon: Microscope, key: "researcher" },
- { name: "Friend", icon: Users, key: "friend" },
- { name: "Security", icon: Shield, key: "security" },
- { name: "Degen Trader", icon: TrendingUp, key: "degenTrader" },
- { name: "Crypto", icon: Bitcoin, key: "crypto" },
- { name: "Personal Assistant", icon: Briefcase, key: "personalAssistant" },
- ]
- export function AgentGrid({ agents, onUpdateAgent }: AgentGridProps) {
- const [searchQuery, setSearchQuery] = useState("")
- const [sortOption, setSortOption] = useState("all")
- const [selectedCategory, setSelectedCategory] = useState("all")
- const [diagnosisRunning, setDiagnosisRunning] = useState(false)
- const [abortController, setAbortController] = useState<AbortController | null>(null)
- const cardRefs = useRef<(AgentCardHandle | null)[]>([])
- const handleCancel = () => {
- if (abortController) {
- abortController.abort()
- setAbortController(null)
- }
- }
- const handleRunAllDiagnoses = async () => {
- if (!diagnosisRunning) {
- const controller = new AbortController()
- setAbortController(controller)
- setDiagnosisRunning(true)
- for (let i = 0; i < cardRefs.current.length; i++) {
- if (controller.signal.aborted) {
- break
- }
- const ref = cardRefs.current[i]
- if (ref && typeof ref.runDiagnosis === "function") {
- try {
- await ref.runDiagnosis(controller.signal)
- } catch (error) {
- if ((error as Error).name === "AbortError") {
- break
- }
- console.error(`Error in agent diagnosis:`, error)
- }
- }
- }
- setDiagnosisRunning(false)
- setAbortController(null)
- }
- }
- // Filter agents based on category, search query and sort
- const filteredAgents = useMemo(() => {
- let result = [...agents]
- // Category filter
- if (selectedCategory !== "all") {
- result = result.filter(agent => agent.category === selectedCategory)
- }
- // Search filter
- if (searchQuery) {
- result = result.filter(agent =>
- agent.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
- agent.description?.toLowerCase().includes(searchQuery.toLowerCase())
- )
- }
- // Sort
- if (sortOption === "price-asc") {
- result = result.sort((a, b) => (a.price || 0) - (b.price || 0))
- } else if (sortOption === "price-desc") {
- result = result.sort((a, b) => (b.price || 0) - (a.price || 0))
- }
- return result
- }, [searchQuery, agents, sortOption, selectedCategory])
- return (
- <div className="container mx-auto px-4 py-8">
- <motion.div initial={{ opacity: 0, y: -20 }} animate={{ opacity: 1, y: 0 }} className="flex flex-col gap-8">
- {/* Search and Filters */}
- <div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
- <div className="flex-1 max-w-md">
- <Input
- placeholder="Search agents..."
- className="w-full border-2 focus:border-blue-500"
- value={searchQuery}
- onChange={(e) => setSearchQuery(e.target.value)}
- />
- </div>
- <div className="flex flex-col sm:flex-row gap-4">
- {/* Leaderboard button */}
- <Link href={`/leaderboard`}>
- <Button
- variant="outline"
- size="sm"
- className="w-full h-10 sm:w-[180px] border-gray-200 text-black hover:bg-neon-pink/50 hover:text-white transition-colors"
- >
- <ClipboardList className="h-4 w-4 mr-2" />
- Leaderboard
- </Button>
- </Link>
- {/* Sort by Price */}
- <Select value={sortOption} onValueChange={(value) => setSortOption(value)}>
- <SelectTrigger className="w-full sm:w-[180px] border-2 bg-white">
- <SelectValue placeholder="Sort by" />
- </SelectTrigger>
- <SelectContent className="bg-white border-2 border-gray-200">
- <SelectItem value="all">Sort by Price</SelectItem>
- <SelectItem value="price-asc">Price: Low to High</SelectItem>
- <SelectItem value="price-desc">Price: High to Low</SelectItem>
- </SelectContent>
- </Select>
- </div>
- </div>
- {/* Categories */}
- <div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-8 gap-4">
- {categories.map((category, index) => {
- const Icon = category.icon
- const isSelected = selectedCategory === category.key
- return (
- <motion.div
- key={category.key}
- initial={{ opacity: 0, y: 20 }}
- animate={{ opacity: 1, y: 0 }}
- transition={{ delay: index * 0.05 }}
- >
- <Button
- onClick={() => setSelectedCategory(category.key)}
- variant="outline"
- className={`h-auto py-4 w-full flex flex-col gap-2 transition-colors ${isSelected ? "bg-blue-100 border-blue-500" : "hover:bg-blue-50"
- }`}
- >
- <Icon className="h-6 w-6 text-blue-500" />
- <span className="text-sm">{category.name}</span>
- </Button>
- </motion.div>
- )
- })}
- </div>
- {/* Featured Agents Section */}
- <div>
- <h2 className="text-2xl font-semibold mb-6 text-blue-900">Personas</h2>
- <div className="flex flex-col sm:flex-row gap-4 mb-6">
- <Button
- variant="outline"
- size="sm"
- className={`w-full sm:w-[180px] border-gray-200 text-black ${diagnosisRunning ? 'hover:bg-neon-pink/50' : 'hover:bg-neon-blue/50'} hover:text-white transition-colors`}
- onClick={diagnosisRunning ? handleCancel : handleRunAllDiagnoses}
- disabled={false}
- >
- {diagnosisRunning ? (
- <>
- <svg
- className={`animate-spin h-4 w-4 mr-2 text-current`}
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- >
- <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
- <path
- className="opacity-75"
- fill="currentColor"
- d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
- />
- </svg>
- Cancel
- </>
- ) : (
- <>
- <RefreshCcw className="h-4 w-4 mr-2" />
- Sync Personas
- </>
- )}
- </Button>
- </div>
- <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
- {filteredAgents.length > 0 ? (
- filteredAgents.map((agent, index) => (
- <AgentCard key={agent.id} agent={agent} onUpdateAgent={onUpdateAgent} index={index} ref={(el) => { cardRefs.current[index] = el; }} />
- ))
- ) : searchQuery.length > 0 && filteredAgents.length < 1 ? (
- <motion.p
- className="text-gray-500 col-span-full text-center"
- initial={{ opacity: 0, y: 10 }}
- animate={{ opacity: 1, y: 0 }}
- transition={{ duration: 0.3 }}
- >
- No agents match your search.
- </motion.p>
- ) : (
- agents.map((agent, index) => (
- <AgentCard key={agent.agentId} agent={agent} onUpdateAgent={onUpdateAgent} index={index} ref={(el) => { cardRefs.current[index] = el; }} />
- ))
- )}
- </div>
- </div>
- </motion.div>
- </div>
- )
- }
|