"use client"; import { memo, useEffect, useState } 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 图片", "snapgen-image": "SnapGen 图片", "openai-image": "OpenAI 图片", bananaimage: "BananaImage 图片", visionary: "Visionary 图片", atlascloud: "Atlas Cloud 图片", "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 = [ "正在读取参考图与文字需求。", "正在分析主体、场景和构图关系。", "正在提取画面中的关键视觉特征。", "正在确认图片比例和输出尺寸。", "正在匹配当前模型的最佳生成参数。", "正在把参考图信息提交给模型。", "正在等待模型完成第一阶段推理。", "正在生成初始画面结构。", "正在保持主体特征和参考一致。", "正在补全画面中的空间层次。", "正在调整光影方向和明暗关系。", "正在优化主体边缘与背景融合。", "正在细化材质、纹理和局部细节。", "正在减少画面噪点和不自然区域。", "正在提升清晰度与整体质感。", "正在检查生成结果的完整性。", "正在处理高分辨率输出。", "正在等待服务商返回最终图片。", "正在保存图片并同步到对话。", "正在准备把结果显示到画布。", ]; const LONG_WAIT_SECONDS = 5 * 60; const LONG_WAIT_HINT = "服务繁忙,模型可能正在排队或自动尝试备用通道,请耐心等候,结果返回后会自动同步到画布。"; function getLongProcessingHint(phase: NonNullable, elapsedSeconds: number) { if (phase !== "processing" && phase !== "finalizing") return PHASE_HINT[phase]; if (elapsedSeconds >= LONG_WAIT_SECONDS) return LONG_WAIT_HINT; 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, queuePosition, sameModelRunningCount, concurrencyLimit, estimatedWaitMs, }: ImageGenerationLoadingProps) => { const shouldTrackElapsed = phase === "queued" || phase === "processing" || phase === "finalizing"; const [internalElapsedSeconds, setInternalElapsedSeconds] = useState(0); useEffect(() => { if (typeof elapsedSeconds === "number") return; if (!shouldTrackElapsed) { setInternalElapsedSeconds(0); return; } const startedAt = Date.now(); setInternalElapsedSeconds(0); const timer = window.setInterval(() => { setInternalElapsedSeconds(Math.floor((Date.now() - startedAt) / 1000)); }, 1000); return () => window.clearInterval(timer); }, [elapsedSeconds, modelName, phase, providerType, shouldTrackElapsed]); const count = Math.min(Math.max(imageCount, 1), maxImages); const resolvedModelName = modelName?.trim() || PROVIDER_MODEL_LABEL[providerType || ""] || "AI 图片模型"; const resolvedElapsedSeconds = typeof elapsedSeconds === "number" ? elapsedSeconds : internalElapsedSeconds; const isLongWait = resolvedElapsedSeconds >= LONG_WAIT_SECONDS && (phase === "processing" || phase === "finalizing"); const estimateRangeLabel = isLongWait ? "单次最长约10分钟" : providerType === "grsai-gpt-image" ? "2~5分钟" : "60~120秒"; const elapsedLabel = resolvedElapsedSeconds > 0 ? `已等待 ${resolvedElapsedSeconds}s` : `预计 ${estimateRangeLabel}`; const phaseHint = getLongProcessingHint(phase, resolvedElapsedSeconds); 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} title={isLongWait ? "服务繁忙,排队处理中" : undefined} />
使用 {resolvedModelName} 生成图片,预计 {estimateRangeLabel}
{queueLine ?
{queueLine}
: null} {queueDetailLine ?
{queueDetailLine}
: null} {(phase !== "processing" || resolvedElapsedSeconds > 0) && (
{phaseHint} {elapsedLabel}
)}
); } ); ImageGenerationLoading.displayName = "ImageGenerationLoading";