ai-chatbot/components/canvas/nodes/AgentSlotNode.tsx

89 lines
3.3 KiB
TypeScript

"use client";
import { ImagePlus, MonitorSmartphone, Sparkles, Video } from "lucide-react";
import type { AgentSlotNodeData, CanvasNode } from "@/types/canvas";
import { cn } from "@/lib/utils";
interface AgentSlotNodeProps {
node: CanvasNode;
isSelected: boolean;
}
function getSlotIcon(slotType: AgentSlotNodeData["slotType"]) {
if (slotType === "video") return Video;
if (slotType === "ui-screen") return MonitorSmartphone;
return ImagePlus;
}
function getSlotTypeLabel(slotType: AgentSlotNodeData["slotType"]) {
if (slotType === "video") return "视频槽位";
if (slotType === "ui-screen") return "UI 槽位";
return "图片槽位";
}
function getStatusLabel(status: AgentSlotNodeData["status"]) {
if (status === "generating") return "生成中";
if (status === "completed") return "已填充";
if (status === "failed") return "失败";
return "等待生成";
}
export function AgentSlotNode({ node, isSelected }: AgentSlotNodeProps) {
const data = node.data as AgentSlotNodeData;
const slotType = data.slotType || "image";
const SlotIcon = getSlotIcon(slotType);
const filledCount = Array.isArray(data.filledNodeIds)
? data.filledNodeIds.length
: 0;
return (
<div
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-none border-2 border-dashed bg-white/92 text-slate-700 shadow-[0_12px_28px_rgba(15,23,42,0.08)] transition-all dark:bg-zinc-950/92 dark:text-slate-100",
isSelected
? "border-[#5b76fe] ring-4 ring-[#5b76fe]/12"
: "border-[#b8c3d8] hover:border-[#8ea2d6] dark:border-zinc-700",
data.status === "generating" && "border-[#7c9cff] bg-[#f7f9ff]",
data.status === "failed" && "border-red-300 bg-red-50 text-red-900"
)}
data-agent-slot-node="true"
data-slot-type={slotType}
>
<div className="flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-5 py-4 text-center">
<div
className={cn(
"flex h-12 w-12 items-center justify-center rounded-full border border-[#dbe2ee] bg-white text-[#5b76fe] shadow-sm dark:border-zinc-700 dark:bg-zinc-900",
data.status === "generating" && "animate-pulse",
data.status === "failed" && "border-red-200 text-red-600"
)}
>
{data.status === "generating" ? (
<Sparkles className="h-5 w-5" />
) : (
<SlotIcon className="h-5 w-5" />
)}
</div>
<div className="min-w-0 space-y-1">
<div className="truncate font-semibold text-[15px] leading-5">
{data.label || getSlotTypeLabel(slotType)}
</div>
<div className="text-[12px] leading-5 text-slate-500 dark:text-slate-400">
{getStatusLabel(data.status)}
{filledCount > 0 ? ` · ${filledCount} 个结果` : ""}
</div>
</div>
</div>
<div className="flex items-center justify-between border-[#e6ebf5] border-t px-3 py-2 text-[11px] text-slate-500 dark:border-zinc-800 dark:text-slate-400">
<span className="truncate">{data.aspectRatio || "1:1"}</span>
<span className="truncate">
{data.fillPolicy === "append-version"
? "追加版本"
: data.fillPolicy === "replace-existing"
? "允许替换"
: "填充空槽"}
</span>
</div>
</div>
);
}