ai-chatbot/components/canvas/nodes/image-node/image-node-empty-state.tsx

134 lines
4.6 KiB
TypeScript

"use client";
import { AlertCircle, ImagePlus, Loader2 } from "lucide-react";
import type React from "react";
import { GenerationDotPlaceholder } from "@/components/generation-dot-placeholder";
interface ImageNodeEmptyStateProps {
detailProgressLabel?: string;
fileInputRef: React.Ref<HTMLInputElement>;
generationProgress?: number | null;
onImageUpload: (event: React.ChangeEvent<HTMLInputElement>) => void;
onSelectFile: (event: React.MouseEvent<HTMLButtonElement>) => void;
status?: string;
viewportScale?: number;
}
export function ImageNodeDeferredLoadState() {
return (
<div className="flex h-full w-full items-center justify-center bg-slate-50 text-slate-400 text-xs dark:bg-zinc-900 dark:text-zinc-500">
<div className="flex flex-col items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
<span>图片即将进入视口时加载</span>
</div>
</div>
);
}
export function ImageNodeProcessingOverlay({
detailProgressLabel,
generationProgress,
viewportScale,
}: {
detailProgressLabel?: string;
generationProgress?: number | null;
viewportScale?: number;
}) {
return (
<div className="absolute inset-0 overflow-hidden rounded-[inherit] bg-white/96 backdrop-blur-[1px]">
<GenerationDotPlaceholder
className="h-full w-full rounded-none"
compact
phase="processing"
progress={generationProgress}
title={detailProgressLabel || "正在处理图片"}
viewportScale={viewportScale}
/>
</div>
);
}
export function ImageNodeEmptyState({
detailProgressLabel,
fileInputRef,
generationProgress,
onImageUpload,
onSelectFile,
status,
viewportScale,
}: ImageNodeEmptyStateProps) {
if (status === "uploading") {
return (
<div className="flex h-full w-full select-none flex-col items-center justify-center bg-[#f7f9fc] px-6 text-center dark:bg-zinc-900">
<span className="mb-3 flex h-12 w-12 items-center justify-center rounded-[18px] border border-[#dde5f4] bg-white text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.22)] dark:border-white/12 dark:bg-white/8">
<Loader2 className="h-5 w-5 animate-spin" />
</span>
<span className="select-none text-sm font-medium text-[#3f4b63] dark:text-zinc-200">
正在上传图片
</span>
<span className="mt-1 select-none text-xs text-[#7a8395] dark:text-zinc-500">
上传完成后会自动显示
</span>
</div>
);
}
if (status === "generating") {
return (
<GenerationDotPlaceholder
className="h-full w-full rounded-none"
compact
phase="processing"
progress={generationProgress}
title={detailProgressLabel || undefined}
viewportScale={viewportScale}
/>
);
}
if (status === "failed") {
const failedTitle = detailProgressLabel || "生成失败";
return (
<div className="flex h-full w-full select-none flex-col items-center justify-center bg-[#fff7f8] px-6 text-center dark:bg-red-950/40">
<span className="mb-3 flex h-12 w-12 items-center justify-center rounded-[18px] border border-[#f2d7dd] bg-white text-[#d24f63] shadow-[0_0_0_1px_rgba(242,215,221,0.22)]">
<AlertCircle className="h-5 w-5" />
</span>
<span className="select-none text-sm font-medium text-[#c84b5e]">
{failedTitle}
</span>
<span className="mt-1 text-xs text-[#b77480]">
{failedTitle.includes("上传") ? "请重新选择文件" : "请重新上传或再试一次"}
</span>
</div>
);
}
return (
<div className="flex h-full w-full select-none flex-col items-center justify-center bg-[#f7f9fc] px-6 text-center dark:bg-zinc-900">
<span className="mb-3 flex h-12 w-12 items-center justify-center rounded-[18px] border border-[#dde5f4] bg-white text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.22)]">
<ImagePlus className="h-5 w-5" />
</span>
<span className="select-none text-sm font-medium text-[#3f4b63]">
上传图片
</span>
<span className="mb-4 mt-1 select-none text-xs text-[#7a8395]">
添加参考图、角色图或关键帧素材
</span>
<input
accept="image/*"
className="hidden"
onChange={onImageUpload}
ref={fileInputRef}
type="file"
/>
<button
className="cursor-pointer rounded-[14px] border border-[#d7dff1] bg-white px-4 py-2 text-sm font-medium text-[#3954db] shadow-[0_0_0_1px_rgba(219,226,238,0.24)] transition-colors hover:bg-[#f4f7ff]"
onClick={onSelectFile}
type="button"
>
选择文件
</button>
</div>
);
}