| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- "use client"
- import type { Agent } from "@/types/agent"
- import { Card, CardContent, CardHeader } from "@/components/ui/card"
- import { Badge } from "@/components/ui/badge"
- import { Button } from "@/components/ui/button"
- import { MessageSquare, Info } from "lucide-react"
- import Image from "next/image"
- import { motion } from "framer-motion"
- import Link from "next/link"
- import { useDiagnosisRunner } from "@/hooks/use-diagnosis"
- import { cn } from "@/lib/utils"
- import { useState } from "react"
- import { forwardRef, useImperativeHandle } from "react";
- interface AgentCardProps {
- agent: Agent
- onUpdateAgent?: (agent: Agent) => void
- index: number
- }
- export interface AgentCardHandle {
- runDiagnosis: (signal: AbortSignal) => Promise<void>;
- }
- const AMICA_URL = process.env.NEXT_PUBLIC_AMICA_URL as string;
- export const AgentCard = forwardRef<AgentCardHandle, AgentCardProps>(({ agent, onUpdateAgent, index }, ref) => {
- const { status, checking, handleDiagnosis } = useDiagnosisRunner(agent, index, true);
- const [diagnosed, setDiagnosed] = useState(false);
- const runDiagnosis = async (signal: AbortSignal): Promise<void> => {
- return new Promise<void>(async (resolve, reject) => {
- if (signal.aborted) {
- return reject(new DOMException("Aborted", "AbortError"));
- }
- signal.addEventListener("abort", () => {
- console.log("Diagnosis aborted");
- reject(new DOMException("Aborted", "AbortError"));
- });
- setDiagnosed(true);
- await handleDiagnosis(false);
- resolve();
- });
- };
- useImperativeHandle(ref, () => ({
- runDiagnosis,
- }));
- return (
- <motion.div
- initial={{ opacity: 0, y: 20 }}
- animate={{ opacity: 1, y: 0 }}
- transition={{ duration: 0.5, delay: index * 0.1 }}
- whileHover={{ y: -5 }}
- className="h-full"
- >
- <Card className="overflow-hidden group bg-scifi-dark border-neon-blue/20 hover:border-neon-blue/40 transition-colors h-full flex flex-col">
- <CardHeader className="p-0">
- <div className="relative h-[320px] w-full overflow-hidden">
- <motion.div whileHover={{ scale: 1.05 }} transition={{ duration: 0.4 }} className=" relative h-full w-full">
- <Image
- src={agent.avatar || "/placeholder.svg"}
- alt={agent.name}
- className="object-cover object-center"
- fill
- priority
- />
- </motion.div>
- </div>
- </CardHeader>
- <CardContent className="p-4 flex-grow flex flex-col justify-between space-y-4">
- <div className="space-y-2">
- <div className="flex items-center justify-between">
- <h3 className="font-orbitron font-semibold text-lg text-white">{agent.name}</h3>
- <div className="flex items-center gap-2">
- <span className="text-sm font-medium text-neon-pink font-orbitron">{agent.price?.toPrecision(2)} AIUS</span>
- <Badge
- variant="secondary"
- loading={checking}
- className={cn(
- "bg-neon-blue border-0 text-white font-roboto-mono",
- "transition-all duration-300 ease-in-out",
- "hover:bg-neon-blue/90 hover:shadow-[0_0_10px_rgba(0,245,255,0.6)] hover:scale-[1.03]",
- "active:scale-[0.98]",
- "focus-visible:ring-2 focus-visible:ring-neon-blue/50"
- )}
- tabIndex={0}
- >
- {checking ? "loading.." : diagnosed ? status : agent.status}
- </Badge>
- </div>
- </div>
- <div className="space-y-1">
- <p className="text-sm text-blue-100/70 font-roboto-mono">Token: {agent.token}</p>
- <p className="text-sm text-blue-100/70 font-roboto-mono">
- Tier: {agent.tier?.name} (Level {agent.tier?.level})
- </p>
- </div>
- {/* Scrollable Description */}
- <div className="max-h-24 overflow-y-auto pr-1 [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar]:h-4/6 [&::-webkit-scrollbar-track]:bg-neon-blue/25 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-gray-300">
- <p className="text-sm text-blue-100/70 font-roboto-mono whitespace-pre-line">
- {agent.description}
- </p>
- </div>
- </div>
- <div className="grid grid-cols-2 gap-2">
- <Button
- variant="outline"
- size="sm"
- className="w-full font-roboto-mono border-neon-blue/50 text-neon-blue hover:bg-neon-blue/20 hover:text-white transition-colors"
- onClick={() => window.open(`${AMICA_URL}/agent/${agent.id}`, "_blank", "noopener,noreferrer")}
- disabled={diagnosed ? status !== "active" : agent.status !== "active"}
- title={
- (diagnosed ? status !== "active" : agent.status !== "active")
- ? "Chat is disabled: Agent is inactive."
- : ""
- }
- >
- <MessageSquare className="h-4 w-4 mr-2" />
- Chat
- </Button>
- <Link href={`/agent/${agent.agentId}`}>
- <Button
- variant="outline"
- size="sm"
- className="w-full font-roboto-mono border-neon-pink/50 text-neon-pink hover:bg-neon-pink/20 hover:text-white transition-colors"
- >
- <Info className="h-4 w-4 mr-2" />
- Details
- </Button>
- </Link>
- </div>
- </CardContent>
- </Card>
- </motion.div>
- )
- }
- )
- AgentCard.displayName = "AgentCard"
|