134 lines
3.9 KiB
TypeScript
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 };
|