import type { UseChatHelpers } from "@ai-sdk/react"; import equal from "fast-deep-equal"; import { ArrowDownIcon } from "lucide-react"; import { memo, useEffect, useState, useRef } from "react"; import useSWR from "swr"; import { isImageGenerationModelDescriptor, isVideoModelDescriptor, } from "@/lib/ai/model-classification"; import { useMessages } from "@/hooks/use-messages"; import type { PendingGeneration } from "@/hooks/use-pending-generation"; import type { Vote } from "@/lib/db/schema"; import type { ChatMessage } from "@/lib/types"; import { useDataStream } from "./data-stream-provider"; import { GeminiImageLoading } from "./gemini-image-loading"; import { QianwenVideoLoading } from "./qianwen-video-loading"; import { VolcanoVisionLoading } from "./volcano-vision-loading"; import { ImageGenerationLoading } from "./image-generation-loading"; import { Greeting } from "./greeting"; import { PreviewMessage, ThinkingMessage } from "./message"; // 模型数据类型 type DynamicModel = { id: string; providerType: string; modelId: string; displayName: string; supportsImageGen?: boolean; capabilities?: unknown; }; const fetcher = (url: string) => fetch(url).then((res) => res.json()); type MessagesProps = { chatId: string; status: UseChatHelpers["status"]; votes: Vote[] | undefined; messages: ChatMessage[]; setMessages: UseChatHelpers["setMessages"]; regenerate: UseChatHelpers["regenerate"]; sendMessage: UseChatHelpers["sendMessage"]; isReadonly: boolean; isArtifactVisible: boolean; selectedModelId: string; onImageEdit?: (imageUrl: string) => void; onDrawingComplete?: (annotatedImageUrl: string) => void; onImageEnhance?: (imageUrl: string) => void; onImageRmbg?: (imageUrl: string) => void; onImageExtend?: (imageUrl: string) => void; onImageAngleEdit?: (imageUrl: string) => void; onImageWatermarkRemove?: (imageUrl: string) => void; isProcessingEnhance?: boolean; isProcessingRmbg?: boolean; isProcessingExtend?: boolean; isProcessingAngleEdit?: boolean; isProcessingWatermarkRemove?: boolean; pendingGeneration?: PendingGeneration | null; // 🆕 持久化的生成状态 imageCount?: number; // 🆕 用户选择的图片数量 isGeneratingImageFlag?: boolean; // 🆕 正在生成图片的标志(来自 Chat 组件) imageGenerationPhase?: "idle" | "queued" | "processing" | "finalizing" | "failed"; imageGenerationStartedAt?: number | null; }; function PureMessages({ chatId, status, votes, messages, setMessages, regenerate, sendMessage, isReadonly, selectedModelId, onImageEdit, onDrawingComplete, onImageEnhance, onImageRmbg, onImageExtend, onImageAngleEdit, onImageWatermarkRemove, isProcessingEnhance = false, isProcessingRmbg = false, isProcessingExtend = false, isProcessingAngleEdit = false, isProcessingWatermarkRemove = false, pendingGeneration, imageCount = 1, isGeneratingImageFlag = false, imageGenerationPhase = "idle", imageGenerationStartedAt = null, }: MessagesProps) { const { containerRef: messagesContainerRef, endRef: messagesEndRef, isAtBottom, scrollToBottom, hasSentMessage, } = useMessages({ status, }); useDataStream(); // 获取模型列表以判断当前模型类型 const { data: modelsData } = useSWR<{ models: DynamicModel[] }>( "/api/models", fetcher ); // 判断当前选中的模型类型 // 🆕 包含 Gemini 和 Grsai GPT Image 图片生成 provider const isGeminiModel = modelsData?.models?.some( (m: DynamicModel) => m.id === selectedModelId && ( m.providerType === "gemini" || m.providerType === "gemini-image" || m.providerType === "grsai-gpt-image" || m.providerType === "huanke-gemini" ) ) ?? false; const isQianwenVideoModel = modelsData?.models?.some( (m: DynamicModel) => m.id === selectedModelId && (m.providerType === "qianwen-video" || m.providerType === "fal-video" || m.providerType === "ruxa-video" || m.providerType === "krea") ) ?? false; const isVolcanoVisionModel = modelsData?.models?.some( (m: DynamicModel) => m.id === selectedModelId && m.providerType === "volcano-vision" ) ?? false; // 🆕 判断是否是阿里生图模型(或者智能自动模式默认使用阿里生图占位图) const isAlibabaImageModel = modelsData?.models?.some( (m: DynamicModel) => m.id === selectedModelId && isImageGenerationModelDescriptor({ providerType: m.providerType, modelId: m.modelId, supportsImageGen: m.supportsImageGen, }) ) ?? false; // 🆕 获取当前模型的显示名称 const currentModelDisplayName = modelsData?.models?.find( (m: DynamicModel) => m.id === selectedModelId )?.displayName; const currentModelProviderType = modelsData?.models?.find( (m: DynamicModel) => m.id === selectedModelId )?.providerType; // 判断是否是视频生成模型(通过 providerType 或模型名称) const isVideoModel = modelsData?.models?.some( (m: DynamicModel) => m.id === selectedModelId && isVideoModelDescriptor({ providerType: m.providerType, modelId: m.modelId, supportsImageGen: m.supportsImageGen, }) ) ?? false; // 控制等待动画延迟消失(Gemini 图片模型延迟 4 秒,其他模型 6 秒) const [showThinkingWithDelay, setShowThinkingWithDelay] = useState(false); const [autoImagePlaceholderReady, setAutoImagePlaceholderReady] = useState(false); const autoImagePlaceholderTimerRef = useRef | null>(null); const [imageGenerationElapsedSeconds, setImageGenerationElapsedSeconds] = useState(0); const isImageGenerationActive = imageGenerationPhase === "queued" || imageGenerationPhase === "processing" || imageGenerationPhase === "finalizing"; const stripInternalMarkers = (text: string) => text .replace(/\n*\n*/g, "") .replace(/\n*\n*/g, "") .replace(/\n*\n*/g, "") .trim(); const hasImageContentInMessage = (message?: ChatMessage | null) => { if (!message?.parts || message.parts.length === 0) return false; return message.parts.some((part: any) => { if (part?.type === "text" && typeof part.text === "string") { return ( part.text.includes("![thumb|") || part.text.includes("![thumb|||") || part.text.includes("![Generated Image") ); } if (part?.type === "image" && typeof part.url === "string" && part.url.length > 0) { return true; } if ( part?.type === "file" && typeof part.url === "string" && ((typeof part.mediaType === "string" && part.mediaType.startsWith("image/")) || /\.(png|jpe?g|webp|gif|bmp|svg)(\?|$)/i.test(part.url)) ) { return true; } return false; }); }; useEffect(() => { return () => { if (autoImagePlaceholderTimerRef.current) { clearTimeout(autoImagePlaceholderTimerRef.current); autoImagePlaceholderTimerRef.current = null; } }; }, []); useEffect(() => { if (!isImageGenerationActive || !imageGenerationStartedAt) { setImageGenerationElapsedSeconds(0); return; } const updateElapsed = () => { setImageGenerationElapsedSeconds( Math.max(0, Math.floor((Date.now() - imageGenerationStartedAt) / 1000)) ); }; updateElapsed(); const timer = setInterval(updateElapsed, 1000); return () => clearInterval(timer); }, [isImageGenerationActive, imageGenerationStartedAt]); useEffect(() => { const lastMessage = messages.length > 0 ? messages[messages.length - 1] : null; // 对于视频模型,检查是否有真正的视频内容(包含