lovart3/components/gemini-image-loading.tsx
2025-12-19 15:30:18 +08:00

150 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { memo } from "react";
import { SparklesIcon } from "./icons";
interface GeminiImageLoadingProps {
imageCount?: number; // 🆕 用户选择的图片数量,默认1张
}
/**
* Gemini 图片生成加载占位符组件
* 显示动态数量的 170x200 像素模糊动态效果
*/
export const GeminiImageLoading = memo(({ imageCount = 1 }: GeminiImageLoadingProps) => {
// 确保数量在 1-2 范围内
const count = Math.min(Math.max(imageCount, 1), 2);
return (
<div
className="group/message fade-in w-full animate-in duration-300"
data-role="assistant"
data-testid="message-assistant-gemini-loading"
>
<div className="flex items-start justify-start gap-3">
{/* 头像 */}
<div className="relative -mt-1 flex size-8 shrink-0 items-center justify-center">
<div className="absolute inset-0 rounded-full bg-purple-500/20 animate-ping" />
<div className="absolute inset-0 rounded-full bg-purple-500/10 animate-pulse" />
<div className="relative flex size-8 items-center justify-center rounded-full bg-background ring-2 ring-purple-500/30">
<div className="animate-spin text-purple-500">
<SparklesIcon size={14} />
</div>
</div>
</div>
<div className="flex flex-col gap-3">
{/* 文本提示 */}
<div className="flex items-center gap-1.5">
<span className="text-base font-medium text-foreground">
正在生成 {count} 张图片
</span>
<span className="flex gap-1">
<span
className="h-2 w-2 rounded-full bg-purple-500 animate-bounce"
style={{ animationDelay: "0ms" }}
/>
<span
className="h-2 w-2 rounded-full bg-purple-500 animate-bounce"
style={{ animationDelay: "150ms" }}
/>
<span
className="h-2 w-2 rounded-full bg-purple-500 animate-bounce"
style={{ animationDelay: "300ms" }}
/>
</span>
</div>
{/* 图片占位符 - 横向排列,每个 170x200px */}
<div className="flex gap-3 flex-wrap">
{Array.from({ length: count }, (_, index) => index).map((index) => (
<div
key={index}
className="relative overflow-hidden rounded-xl"
style={{ width: "170px", height: "200px" }}
>
{/* 底层渐变背景 */}
<div className="absolute inset-0 bg-gradient-to-br from-purple-400 via-pink-400 to-blue-400 opacity-60" />
{/* 动态模糊叠加层 1 */}
<div
className="absolute inset-0 bg-gradient-to-tr from-purple-500/40 via-transparent to-pink-500/40 animate-pulse"
style={{ animationDuration: "2s", animationDelay: `${index * 0.3}s` }}
/>
{/* 动态模糊叠加层 2 */}
<div
className="absolute inset-0 bg-gradient-to-bl from-blue-500/30 via-transparent to-purple-500/30 animate-pulse"
style={{ animationDuration: "2.5s", animationDelay: `${0.5 + index * 0.3}s` }}
/>
{/* 噪点纹理效果 */}
<div
className="absolute inset-0 opacity-30"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 400 400' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E")`,
}}
/>
{/* 模糊叠加 */}
<div className="absolute inset-0 backdrop-blur-sm" />
{/* 中心图标 */}
<div className="absolute inset-0 flex items-center justify-center">
<div className="relative">
{/* 外圈光晕 */}
<div className="absolute -inset-4 rounded-full bg-white/20 animate-ping" style={{ animationDuration: "1.5s", animationDelay: `${index * 0.2}s` }} />
{/* 图片图标 */}
<svg
className="h-10 w-10 text-white/80 animate-pulse"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
style={{ animationDuration: "1.5s", animationDelay: `${index * 0.2}s` }}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
</div>
</div>
{/* 流光效果 */}
<div
className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -translate-x-full animate-shimmer"
style={{
animation: "shimmer 2s infinite",
animationDelay: `${index * 0.4}s`,
}}
/>
</div>
))}
</div>
{/* 提示文字 */}
<span className="text-xs text-muted-foreground">
NanoBananaPro 正在创作 {count} 张图片,请稍候...
</span>
</div>
</div>
{/* 流光动画样式 */}
<style jsx>{`
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
`}</style>
</div>
);
});
GeminiImageLoading.displayName = "GeminiImageLoading";