137 lines
4.3 KiB
TypeScript
137 lines
4.3 KiB
TypeScript
"use client";
|
|
|
|
import { Copy, Pencil, Sparkles, Trash2, Video } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { cn } from "@/lib/utils";
|
|
import type { AgentPreset } from "@/types/agent";
|
|
|
|
interface AgentPresetCardProps {
|
|
preset: AgentPreset;
|
|
selected?: boolean;
|
|
onSelect: (presetId: string) => void;
|
|
onFork?: (preset: AgentPreset) => void;
|
|
onEdit?: (preset: AgentPreset) => void;
|
|
onDelete?: (preset: AgentPreset) => void;
|
|
}
|
|
|
|
export function AgentPresetCard({
|
|
preset,
|
|
selected = false,
|
|
onSelect,
|
|
onFork,
|
|
onEdit,
|
|
onDelete,
|
|
}: AgentPresetCardProps) {
|
|
const previewUrl =
|
|
preset.avatarUrl ||
|
|
preset.referenceAssets?.[0]?.thumbnailUrl ||
|
|
preset.referenceAssets?.[0]?.originalUrl ||
|
|
"";
|
|
const ownerName =
|
|
String(preset.owner?.name || "").trim() ||
|
|
String(preset.owner?.email || "").trim() ||
|
|
"未知发布人";
|
|
const publishedAt = (() => {
|
|
const raw = preset.createdAt ? new Date(preset.createdAt) : null;
|
|
if (!raw || Number.isNaN(raw.getTime())) return "未知时间";
|
|
return raw.toLocaleDateString("zh-CN", {
|
|
year: "numeric",
|
|
month: "2-digit",
|
|
day: "2-digit",
|
|
});
|
|
})();
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"group overflow-hidden rounded-2xl border bg-background transition-all",
|
|
selected
|
|
? "border-primary bg-primary/5 shadow-[0_0_0_1px_rgba(59,130,246,0.12)]"
|
|
: "border-border hover:border-primary/40 hover:shadow-sm"
|
|
)}
|
|
>
|
|
<button
|
|
type="button"
|
|
className="block w-full text-left"
|
|
onClick={() => onSelect(preset.id)}
|
|
>
|
|
<div className="relative aspect-[4/3] w-full overflow-hidden bg-muted/40">
|
|
{previewUrl ? (
|
|
<img
|
|
src={previewUrl}
|
|
alt={preset.name}
|
|
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
|
|
/>
|
|
) : (
|
|
<div className="flex h-full w-full items-center justify-center">
|
|
{preset.type === "video" ? (
|
|
<Video className="h-8 w-8 text-muted-foreground/50" />
|
|
) : (
|
|
<Sparkles className="h-8 w-8 text-muted-foreground/50" />
|
|
)}
|
|
</div>
|
|
)}
|
|
<div className="absolute left-2 top-2 flex flex-wrap gap-1.5">
|
|
<Badge variant="secondary" className="bg-white/90 text-black shadow-sm">
|
|
{preset.type === "video" ? "视频" : "生图"}
|
|
</Badge>
|
|
{preset.category?.name ? (
|
|
<Badge variant="secondary" className="bg-white/90 text-black shadow-sm">
|
|
{preset.category.name}
|
|
</Badge>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2 p-3">
|
|
<div className="line-clamp-1 text-sm font-semibold">{preset.name}</div>
|
|
<div className="flex items-center justify-between gap-3 text-xs text-muted-foreground">
|
|
<span className="truncate">{ownerName}</span>
|
|
<span className="shrink-0">{publishedAt}</span>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
|
|
<div className="flex items-center gap-2 border-t px-3 py-2">
|
|
<Button size="sm" className="h-8 flex-1" onClick={() => onSelect(preset.id)}>
|
|
{selected ? "已选中" : "使用"}
|
|
</Button>
|
|
{preset.visibility === "private" && onEdit ? (
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="h-8"
|
|
onClick={() => onEdit(preset)}
|
|
>
|
|
<Pencil className="mr-1 h-3.5 w-3.5" />
|
|
编辑
|
|
</Button>
|
|
) : null}
|
|
{preset.visibility === "private" && onDelete ? (
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="h-8 text-destructive hover:text-destructive"
|
|
onClick={() => onDelete(preset)}
|
|
>
|
|
<Trash2 className="mr-1 h-3.5 w-3.5" />
|
|
删除
|
|
</Button>
|
|
) : null}
|
|
{preset.visibility === "public" && onFork ? (
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="h-8"
|
|
onClick={() => onFork(preset)}
|
|
>
|
|
<Copy className="mr-1 h-3.5 w-3.5" />
|
|
另存
|
|
</Button>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|