ai-chatbot/components/image-generation-loading.tsx
Your Name bbdda81686
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: add midjourney provider and failover exclusion
2026-03-07 16:25:48 +08:00

137 lines
4.8 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 { ChevronDown, Grid2x2, CircleDot } from "lucide-react";
import { cn } from "@/lib/utils";
type ColorTheme = "purple" | "blue" | "green" | "orange" | "red";
interface ImageGenerationLoadingProps {
imageCount?: number;
maxImages?: number;
colorTheme?: ColorTheme;
modelName?: string;
providerType?: string;
phase?: "idle" | "queued" | "processing" | "finalizing" | "failed";
elapsedSeconds?: number;
}
const PROVIDER_MODEL_LABEL: Record<string, string> = {
"alibaba-image": "千问图片",
"ruxa-image": "RUXA 图片",
"fal-image": "FAL 图片",
"openai-image": "OpenAI 图片",
midjourney: "Midjourney",
"volcano-vision": "即梦 4.5",
gemini: "Gemini 图片",
};
const PHASE_HINT: Record<NonNullable<ImageGenerationLoadingProps["phase"]>, string> = {
idle: "任务已创建,正在初始化生成参数。",
queued: "任务已进入队列,请稍候。",
processing: "模型正在生成图片,请稍候。",
finalizing: "图片已生成完成,正在同步到对话。",
failed: "生成失败,请重试。",
};
export const ImageGenerationLoading = memo(
({
imageCount = 1,
maxImages = 6,
modelName,
providerType,
phase = "processing",
elapsedSeconds = 0,
}: ImageGenerationLoadingProps) => {
const count = Math.min(Math.max(imageCount, 1), maxImages);
const resolvedModelName =
modelName?.trim() || PROVIDER_MODEL_LABEL[providerType || ""] || "AI 图片模型";
const estimateRangeLabel = "60~120秒";
const elapsedLabel =
elapsedSeconds > 0 ? `已等待 ${elapsedSeconds}s` : `预计 ${estimateRangeLabel}`;
const isFailed = phase === "failed";
return (
<div
className="group/message fade-in w-full animate-in duration-300"
data-role="assistant"
data-testid="message-assistant-image-loading"
>
<div className="w-full max-w-[560px] space-y-2.5">
<div className="flex items-center gap-2 text-[13px] text-[#9aa3b2]">
<ChevronDown className="h-4 w-4" />
<span className="font-medium">开始生成</span>
</div>
<div className="flex items-center gap-2 text-[#a8b1bf]">
<CircleDot className="h-3.5 w-3.5 fill-current" />
<span className="text-[14px]">{resolvedModelName}</span>
</div>
<div
className={cn(
"relative overflow-hidden rounded-[22px] border border-[#e1e4ea] bg-[#eaedf2]",
count === 1 ? "h-[420px] w-full max-w-[340px]" : "grid gap-3 p-3",
count > 1 && "grid-cols-2"
)}
>
{count === 1 ? (
<>
<div className="absolute inset-0 bg-gradient-to-r from-[#e9ecf1] via-[#dfe4eb] to-[#e9ecf1] opacity-90" />
<div className="generation-skeleton-shimmer absolute inset-0 bg-[linear-gradient(105deg,transparent_0%,rgba(255,255,255,0.45)_48%,transparent_100%)]" />
</>
) : (
Array.from({ length: count }).map((_, index) => (
<div
key={index}
className="relative h-[180px] overflow-hidden rounded-2xl border border-[#dfe3e9] bg-[#e7ebf0]"
>
<div className="absolute inset-0 bg-gradient-to-r from-[#e7ebf0] via-[#dce2ea] to-[#e7ebf0]" />
<div
className="generation-skeleton-shimmer absolute inset-0 bg-[linear-gradient(105deg,transparent_0%,rgba(255,255,255,0.42)_48%,transparent_100%)]"
style={{ animationDelay: `${index * 0.2}s` }}
/>
</div>
))
)}
</div>
<div className="flex items-center gap-2 pt-1 text-[13px] text-[#9aa3b2]">
<Grid2x2 className="h-5 w-5" />
<span className="font-medium">{isFailed ? "生成失败" : "设计图处理中"}</span>
</div>
<div className="space-y-0.5 text-[#9aa3b2]">
<div className="text-[13px] font-medium">
使用 {resolvedModelName} 生成图片,预计 {estimateRangeLabel}
</div>
{(phase !== "processing" || elapsedSeconds > 0) && (
<div className="text-[13px]">
{PHASE_HINT[phase]} {elapsedLabel}
</div>
)}
</div>
</div>
<style jsx>{`
.generation-skeleton-shimmer {
transform: translateX(-100%);
animation: generation-shimmer 1.8s ease-in-out infinite;
}
@keyframes generation-shimmer {
0% {
transform: translateX(-110%);
}
100% {
transform: translateX(110%);
}
}
`}</style>
</div>
);
}
);
ImageGenerationLoading.displayName = "ImageGenerationLoading";