agent-grid.tsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. "use client"
  2. import type { Agent } from "@/types/agent"
  3. import { AgentCard, AgentCardHandle } from "./agent-card"
  4. import { Input } from "@/components/ui/input"
  5. import { Button } from "@/components/ui/button"
  6. import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
  7. import { Brain, Code, Microscope, Users, Shield, TrendingUp, Bitcoin, Briefcase, ClipboardList, RefreshCcw } from "lucide-react"
  8. import { motion } from "framer-motion"
  9. import { useMemo, useRef, useState } from "react"
  10. import Link from "next/link"
  11. interface AgentGridProps {
  12. agents: Agent[]
  13. onUpdateAgent?: (agent: Agent) => void
  14. }
  15. const categories = [
  16. { name: "All Agents", icon: Brain, key: "all" },
  17. { name: "Programmer", icon: Code, key: "programmer" },
  18. { name: "Researcher", icon: Microscope, key: "researcher" },
  19. { name: "Friend", icon: Users, key: "friend" },
  20. { name: "Security", icon: Shield, key: "security" },
  21. { name: "Degen Trader", icon: TrendingUp, key: "degenTrader" },
  22. { name: "Crypto", icon: Bitcoin, key: "crypto" },
  23. { name: "Personal Assistant", icon: Briefcase, key: "personalAssistant" },
  24. ]
  25. export function AgentGrid({ agents, onUpdateAgent }: AgentGridProps) {
  26. const [searchQuery, setSearchQuery] = useState("")
  27. const [sortOption, setSortOption] = useState("all")
  28. const [selectedCategory, setSelectedCategory] = useState("all")
  29. const [diagnosisRunning, setDiagnosisRunning] = useState(false)
  30. const [abortController, setAbortController] = useState<AbortController | null>(null)
  31. const cardRefs = useRef<(AgentCardHandle | null)[]>([])
  32. const handleCancel = () => {
  33. if (abortController) {
  34. abortController.abort()
  35. setAbortController(null)
  36. }
  37. }
  38. const handleRunAllDiagnoses = async () => {
  39. if (!diagnosisRunning) {
  40. const controller = new AbortController()
  41. setAbortController(controller)
  42. setDiagnosisRunning(true)
  43. for (let i = 0; i < cardRefs.current.length; i++) {
  44. if (controller.signal.aborted) {
  45. break
  46. }
  47. const ref = cardRefs.current[i]
  48. if (ref && typeof ref.runDiagnosis === "function") {
  49. try {
  50. await ref.runDiagnosis(controller.signal)
  51. } catch (error) {
  52. if ((error as Error).name === "AbortError") {
  53. break
  54. }
  55. console.error(`Error in agent diagnosis:`, error)
  56. }
  57. }
  58. }
  59. setDiagnosisRunning(false)
  60. setAbortController(null)
  61. }
  62. }
  63. // Filter agents based on category, search query and sort
  64. const filteredAgents = useMemo(() => {
  65. let result = [...agents]
  66. // Category filter
  67. if (selectedCategory !== "all") {
  68. result = result.filter(agent => agent.category === selectedCategory)
  69. }
  70. // Search filter
  71. if (searchQuery) {
  72. result = result.filter(agent =>
  73. agent.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
  74. agent.description?.toLowerCase().includes(searchQuery.toLowerCase())
  75. )
  76. }
  77. // Sort
  78. if (sortOption === "price-asc") {
  79. result = result.sort((a, b) => (a.price || 0) - (b.price || 0))
  80. } else if (sortOption === "price-desc") {
  81. result = result.sort((a, b) => (b.price || 0) - (a.price || 0))
  82. }
  83. return result
  84. }, [searchQuery, agents, sortOption, selectedCategory])
  85. return (
  86. <div className="container mx-auto px-4 py-8">
  87. <motion.div initial={{ opacity: 0, y: -20 }} animate={{ opacity: 1, y: 0 }} className="flex flex-col gap-8">
  88. {/* Search and Filters */}
  89. <div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
  90. <div className="flex-1 max-w-md">
  91. <Input
  92. placeholder="Search agents..."
  93. className="w-full border-2 focus:border-blue-500"
  94. value={searchQuery}
  95. onChange={(e) => setSearchQuery(e.target.value)}
  96. />
  97. </div>
  98. <div className="flex flex-col sm:flex-row gap-4">
  99. {/* Leaderboard button */}
  100. <Link href={`/leaderboard`}>
  101. <Button
  102. variant="outline"
  103. size="sm"
  104. className="w-full h-10 sm:w-[180px] border-gray-200 text-black hover:bg-neon-pink/50 hover:text-white transition-colors"
  105. >
  106. <ClipboardList className="h-4 w-4 mr-2" />
  107. Leaderboard
  108. </Button>
  109. </Link>
  110. {/* Sort by Price */}
  111. <Select value={sortOption} onValueChange={(value) => setSortOption(value)}>
  112. <SelectTrigger className="w-full sm:w-[180px] border-2 bg-white">
  113. <SelectValue placeholder="Sort by" />
  114. </SelectTrigger>
  115. <SelectContent className="bg-white border-2 border-gray-200">
  116. <SelectItem value="all">Sort by Price</SelectItem>
  117. <SelectItem value="price-asc">Price: Low to High</SelectItem>
  118. <SelectItem value="price-desc">Price: High to Low</SelectItem>
  119. </SelectContent>
  120. </Select>
  121. </div>
  122. </div>
  123. {/* Categories */}
  124. <div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-8 gap-4">
  125. {categories.map((category, index) => {
  126. const Icon = category.icon
  127. const isSelected = selectedCategory === category.key
  128. return (
  129. <motion.div
  130. key={category.key}
  131. initial={{ opacity: 0, y: 20 }}
  132. animate={{ opacity: 1, y: 0 }}
  133. transition={{ delay: index * 0.05 }}
  134. >
  135. <Button
  136. onClick={() => setSelectedCategory(category.key)}
  137. variant="outline"
  138. 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"
  139. }`}
  140. >
  141. <Icon className="h-6 w-6 text-blue-500" />
  142. <span className="text-sm">{category.name}</span>
  143. </Button>
  144. </motion.div>
  145. )
  146. })}
  147. </div>
  148. {/* Featured Agents Section */}
  149. <div>
  150. <h2 className="text-2xl font-semibold mb-6 text-blue-900">Personas</h2>
  151. <div className="flex flex-col sm:flex-row gap-4 mb-6">
  152. <Button
  153. variant="outline"
  154. size="sm"
  155. 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`}
  156. onClick={diagnosisRunning ? handleCancel : handleRunAllDiagnoses}
  157. disabled={false}
  158. >
  159. {diagnosisRunning ? (
  160. <>
  161. <svg
  162. className={`animate-spin h-4 w-4 mr-2 text-current`}
  163. xmlns="http://www.w3.org/2000/svg"
  164. fill="none"
  165. viewBox="0 0 24 24"
  166. >
  167. <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
  168. <path
  169. className="opacity-75"
  170. fill="currentColor"
  171. d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
  172. />
  173. </svg>
  174. Cancel
  175. </>
  176. ) : (
  177. <>
  178. <RefreshCcw className="h-4 w-4 mr-2" />
  179. Sync Personas
  180. </>
  181. )}
  182. </Button>
  183. </div>
  184. <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
  185. {filteredAgents.length > 0 ? (
  186. filteredAgents.map((agent, index) => (
  187. <AgentCard key={agent.id} agent={agent} onUpdateAgent={onUpdateAgent} index={index} ref={(el) => { cardRefs.current[index] = el; }} />
  188. ))
  189. ) : searchQuery.length > 0 && filteredAgents.length < 1 ? (
  190. <motion.p
  191. className="text-gray-500 col-span-full text-center"
  192. initial={{ opacity: 0, y: 10 }}
  193. animate={{ opacity: 1, y: 0 }}
  194. transition={{ duration: 0.3 }}
  195. >
  196. No agents match your search.
  197. </motion.p>
  198. ) : (
  199. agents.map((agent, index) => (
  200. <AgentCard key={agent.agentId} agent={agent} onUpdateAgent={onUpdateAgent} index={index} ref={(el) => { cardRefs.current[index] = el; }} />
  201. ))
  202. )}
  203. </div>
  204. </div>
  205. </motion.div>
  206. </div>
  207. )
  208. }