"use client"; import { BotIcon, MessageSquareIcon, Trash2Icon } from "lucide-react"; import { useRouter } from "next/navigation"; import { type ComponentProps, type ReactElement, useState } from "react"; import { toast } from "sonner"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { useDeleteAgent } from "@/core/agents"; import type { Agent } from "@/core/agents"; import { useI18n } from "@/core/i18n/hooks"; import { cn } from "@/lib/utils"; interface AgentCardProps { agent: Agent; } /** * Reveals the full text in a tooltip ONLY when its trigger is actually clipped. * Clipping is measured on pointer enter against the trigger's own box, covering * both single-line `truncate` (width) and multi-line `line-clamp` (height), so * untruncated content never pops a redundant tooltip. */ function TruncatedTooltip({ text, children, }: { text: string; children: ReactElement; }) { const [truncated, setTruncated] = useState(false); return ( { const el = e.currentTarget; setTruncated( el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight, ); }} > {children} {truncated && ( {text} )} ); } /** * Long, user-controlled labels (agent model, skills, tool groups) that must * never break the card layout: width is capped to the parent and the text is * truncated with an ellipsis, with the full value revealed on hover. */ function TruncatedBadge({ label, variant, className, }: { label: string; variant: ComponentProps["variant"]; className?: string; }) { return ( {label} ); } export function AgentCard({ agent }: AgentCardProps) { const { t } = useI18n(); const router = useRouter(); const deleteAgent = useDeleteAgent(); const [deleteOpen, setDeleteOpen] = useState(false); function handleChat() { router.push(`/workspace/agents/${agent.name}/chats/new`); } async function handleDelete() { try { await deleteAgent.mutateAsync(agent.name); toast.success(t.agents.deleteSuccess); setDeleteOpen(false); } catch (err) { toast.error(err instanceof Error ? err.message : String(err)); } } return ( <> {agent.name} {agent.model && ( )} {agent.description && ( {agent.description} )} {(agent.tool_groups?.length ?? agent.skills?.length ?? 0) > 0 && ( {agent.tool_groups?.map((group) => ( ))} {agent.skills?.map((skill) => ( ))} )} {t.agents.chat} setDeleteOpen(true)} title={t.agents.delete} > {/* Delete Confirm */} {t.agents.delete} {t.agents.deleteConfirm} setDeleteOpen(false)} disabled={deleteAgent.isPending} > {t.common.cancel} {deleteAgent.isPending ? t.common.loading : t.common.delete} > ); }