geo/frontend/components/business/agent-status-card.tsx

134 lines
3.9 KiB
TypeScript

"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
import { Card } from "@/components/ui/card";
export type AgentStatus = "online" | "offline" | "busy" | "error";
export interface AgentStatusCardProps extends React.HTMLAttributes<HTMLDivElement> {
/** Agent名称 */
name: string;
/** Agent描述/类型 */
description?: string;
/** 当前状态 */
status: AgentStatus;
/** 当前执行的任务 */
currentTask?: string;
/** 最近执行时间 */
lastActiveAt?: string;
/** 已完成任务数 */
completedCount?: number;
/** 头像/图标 */
avatar?: React.ReactNode;
}
const statusConfig: Record<AgentStatus, { label: string; dotClass: string; badgeClass: string }> = {
online: {
label: "在线",
dotClass: "bg-primary animate-pulse",
badgeClass: "bg-primary/10 text-primary",
},
offline: {
label: "离线",
dotClass: "bg-muted-foreground",
badgeClass: "bg-muted text-muted-foreground",
},
busy: {
label: "繁忙",
dotClass: "bg-accent animate-pulse",
badgeClass: "bg-accent/10 text-accent",
},
error: {
label: "错误",
dotClass: "bg-destructive",
badgeClass: "bg-destructive/10 text-destructive",
},
};
const AgentStatusCard = React.forwardRef<HTMLDivElement, AgentStatusCardProps>(
(
{
className,
name,
description,
status,
currentTask,
lastActiveAt,
completedCount,
avatar,
...props
},
ref
) => {
const config = statusConfig[status];
return (
<Card
ref={ref}
className={cn("p-4 group", className)}
{...props}
>
<div className="flex items-start gap-3">
{/* Avatar area */}
<div className="relative shrink-0">
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-primary/10 text-primary text-sm font-bold">
{avatar ?? name.slice(0, 2).toUpperCase()}
</div>
{/* Status indicator dot */}
<span
className={cn(
"absolute -bottom-0.5 -right-0.5 block h-3 w-3 rounded-full border-2 border-background",
config.dotClass
)}
/>
</div>
{/* Info area */}
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<p className="text-sm font-semibold text-foreground truncate">{name}</p>
{description && (
<p className="text-xs text-muted-foreground truncate">{description}</p>
)}
</div>
<span
className={cn(
"shrink-0 rounded-md px-2 py-0.5 text-xs font-semibold",
config.badgeClass
)}
>
{config.label}
</span>
</div>
{/* Task info */}
{currentTask && status !== "offline" && (
<div className="mt-2 flex items-center gap-1.5">
<div className="h-1.5 w-1.5 rounded-full bg-primary shrink-0" />
<p className="text-xs text-muted-foreground truncate">{currentTask}</p>
</div>
)}
{/* Footer */}
<div className="mt-2 flex items-center justify-between">
{lastActiveAt && (
<p className="text-xs text-muted-foreground">{lastActiveAt}</p>
)}
{completedCount !== undefined && (
<p className="text-xs text-muted-foreground ml-auto">
<span className="font-semibold text-foreground">{completedCount}</span>
</p>
)}
</div>
</div>
</div>
</Card>
);
}
);
AgentStatusCard.displayName = "AgentStatusCard";
export { AgentStatusCard };