lovart3/components/image-processing-loading.tsx
Your Name 1c7bd9e2d0 feat: 在聊天对话中为图片添加变清晰和抠图功能
- 在图片预览组件左上角添加变清晰和抠图按钮
- 创建图片处理加载组件(类似Gemini生图的动态卡片效果)
- 实现点击按钮后的处理逻辑:创建临时消息、调用API、更新消息
- 添加消息保存API用于保存处理后的图片消息
- 当消息文本为'正在变清晰...'或'正在抠图...'时显示动态加载组件
- 处理完成后替换加载卡片为处理后的图片并保存到数据库
2025-12-29 09:10:52 +08:00

159 lines
5.9 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";
import { Scissors } from "lucide-react";
interface ImageProcessingLoadingProps {
type: "enhance" | "rmbg"; // 处理类型:变清晰或抠图
}
/**
* 图片处理加载占位符组件
* 显示类似 Gemini 生图的动态卡片效果
*/
export const ImageProcessingLoading = memo(({ type }: ImageProcessingLoadingProps) => {
const isEnhance = type === "enhance";
const color = isEnhance ? "blue" : "green";
const text = isEnhance ? "正在变清晰" : "正在抠图";
const icon = isEnhance ? SparklesIcon : Scissors;
const Icon = icon;
// 根据类型设置颜色类名
const colorClasses = isEnhance
? {
ping: "bg-blue-500/20",
pulse: "bg-blue-500/10",
ring: "ring-blue-500/30",
text: "text-blue-500",
dot: "bg-blue-500",
gradientFrom: "from-blue-400",
gradientFromOverlay: "from-blue-500/40",
gradientToOverlay: "to-blue-500/30",
}
: {
ping: "bg-green-500/20",
pulse: "bg-green-500/10",
ring: "ring-green-500/30",
text: "text-green-500",
dot: "bg-green-500",
gradientFrom: "from-green-400",
gradientFromOverlay: "from-green-500/40",
gradientToOverlay: "to-green-500/30",
};
return (
<div
className="group/message fade-in w-full animate-in duration-300"
data-role="assistant"
data-testid="message-assistant-image-processing-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 ${colorClasses.ping} animate-ping`} />
<div className={`absolute inset-0 rounded-full ${colorClasses.pulse} animate-pulse`} />
<div className={`relative flex size-8 items-center justify-center rounded-full bg-background ring-2 ${colorClasses.ring}`}>
<div className={`animate-spin ${colorClasses.text}`}>
<Icon 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">
{text}
</span>
<span className="flex gap-1">
<span
className={`h-2 w-2 rounded-full ${colorClasses.dot} animate-bounce`}
style={{ animationDelay: "0ms" }}
/>
<span
className={`h-2 w-2 rounded-full ${colorClasses.dot} animate-bounce`}
style={{ animationDelay: "150ms" }}
/>
<span
className={`h-2 w-2 rounded-full ${colorClasses.dot} animate-bounce`}
style={{ animationDelay: "300ms" }}
/>
</span>
</div>
{/* 图片占位符 - 170x200px */}
<div className="relative overflow-hidden rounded-xl" style={{ width: "170px", height: "200px" }}>
{/* 底层渐变背景 */}
<div className={`absolute inset-0 bg-gradient-to-br ${colorClasses.gradientFrom} via-purple-400 to-pink-400 opacity-60`} />
{/* 动态模糊叠加层 1 */}
<div
className={`absolute inset-0 bg-gradient-to-tr ${colorClasses.gradientFromOverlay} via-transparent to-purple-500/40 animate-pulse`}
style={{ animationDuration: "2s" }}
/>
{/* 动态模糊叠加层 2 */}
<div
className={`absolute inset-0 bg-gradient-to-bl from-purple-500/30 via-transparent ${colorClasses.gradientToOverlay} animate-pulse`}
style={{ animationDuration: "2.5s", animationDelay: "0.5s" }}
/>
{/* 噪点纹理效果 */}
<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" }} />
{/* 图标 */}
<Icon
className="h-10 w-10 text-white/80 animate-pulse"
style={{ animationDuration: "1.5s" }}
/>
</div>
</div>
{/* 流光效果 */}
<div
className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -translate-x-full"
style={{
animation: "shimmer 2s infinite",
}}
/>
</div>
{/* 提示文字 */}
<span className="text-xs text-muted-foreground">
正在处理图片,请稍候...
</span>
</div>
</div>
{/* 流光动画样式 */}
<style jsx>{`
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
`}</style>
</div>
);
});
ImageProcessingLoading.displayName = "ImageProcessingLoading";