"use client"; import { memo } from "react"; import { ChevronDown, CircleDot } from "lucide-react"; import { GenerationDotPlaceholder } from "@/components/generation-dot-placeholder"; 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; queuePosition?: number; sameModelRunningCount?: number; concurrencyLimit?: number; estimatedWaitMs?: number; } const PROVIDER_MODEL_LABEL: Record = { "alibaba-image": "千问图片", "ruxa-image": "RUXA 图片", "fal-image": "FAL 图片", "openai-image": "OpenAI 图片", "stepfun-image": "StepFun 图片", midjourney: "Midjourney", "volcano-vision": "即梦 4.5", gemini: "Gemini 图片", krea: "Krea 图片", }; const PHASE_HINT: Record, string> = { idle: "任务已创建,正在初始化生成参数。", queued: "任务已进入队列,请稍候。", processing: "模型正在生成图片,请稍候。", finalizing: "图片已生成完成,正在同步到对话。", failed: "生成失败,请重试。", }; const LONG_PROCESSING_HINTS = [ "正在读取参考图与文字需求。", "正在分析主体、场景和构图关系。", "正在提取画面中的关键视觉特征。", "正在确认图片比例和输出尺寸。", "正在匹配当前模型的最佳生成参数。", "正在把参考图信息提交给模型。", "正在等待模型完成第一阶段推理。", "正在生成初始画面结构。", "正在保持主体特征和参考一致。", "正在补全画面中的空间层次。", "正在调整光影方向和明暗关系。", "正在优化主体边缘与背景融合。", "正在细化材质、纹理和局部细节。", "正在减少画面噪点和不自然区域。", "正在提升清晰度与整体质感。", "正在检查生成结果的完整性。", "正在处理高分辨率输出。", "正在等待服务商返回最终图片。", "正在保存图片并同步到对话。", "正在准备把结果显示到画布。", ]; function getLongProcessingHint(phase: NonNullable, elapsedSeconds: number) { if (phase !== "processing" && phase !== "finalizing") return PHASE_HINT[phase]; const index = Math.min( LONG_PROCESSING_HINTS.length - 1, Math.floor(Math.max(0, elapsedSeconds) / 15) ); return LONG_PROCESSING_HINTS[index]; } function formatImageQueueWaitLabel(estimatedWaitMs?: number) { const numeric = Number(estimatedWaitMs || 0); if (!Number.isFinite(numeric) || numeric <= 0) { return "预计即将开始"; } const seconds = Math.max(1, Math.round(numeric / 1000)); if (seconds < 60) { return `预计 ${seconds} 秒`; } const minutes = Math.floor(seconds / 60); const remainSeconds = seconds % 60; return remainSeconds > 0 ? `预计 ${minutes} 分 ${remainSeconds} 秒` : `预计 ${minutes} 分钟`; } export const ImageGenerationLoading = memo( ({ imageCount = 1, maxImages = 6, modelName, providerType, phase = "processing", elapsedSeconds = 0, queuePosition, sameModelRunningCount, concurrencyLimit, estimatedWaitMs, }: ImageGenerationLoadingProps) => { const count = Math.min(Math.max(imageCount, 1), maxImages); const resolvedModelName = modelName?.trim() || PROVIDER_MODEL_LABEL[providerType || ""] || "AI 图片模型"; const estimateRangeLabel = providerType === "grsai-gpt-image" ? "2~5分钟" : "60~120秒"; const elapsedLabel = elapsedSeconds > 0 ? `已等待 ${elapsedSeconds}s` : `预计 ${estimateRangeLabel}`; const phaseHint = getLongProcessingHint(phase, elapsedSeconds); const queueLine = phase === "queued" && Number.isFinite(queuePosition) ? Math.max(0, Number(queuePosition || 0)) > 0 ? `排队中,前方 ${Math.max(0, Number(queuePosition || 0))} 个` : "排队中,等待进入执行" : null; const queueDetailLine = phase === "queued" ? `当前模型运行 ${Math.max( 0, Number(sameModelRunningCount || 0) )}/${Math.max(1, Number(concurrencyLimit || 0) || 1)},${formatImageQueueWaitLabel( estimatedWaitMs )}` : null; return (
开始生成
{resolvedModelName}
1} phase={phase} />
使用 {resolvedModelName} 生成图片,预计 {estimateRangeLabel}
{queueLine ?
{queueLine}
: null} {queueDetailLine ?
{queueDetailLine}
: null} {(phase !== "processing" || elapsedSeconds > 0) && (
{phaseHint} {elapsedLabel}
)}
); } ); ImageGenerationLoading.displayName = "ImageGenerationLoading";