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 { 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; }; 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 组件) }; 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, }: MessagesProps) { const { containerRef: messagesContainerRef, endRef: messagesEndRef, isAtBottom, scrollToBottom, hasSentMessage, } = useMessages({ status, }); useDataStream(); // 获取模型列表以判断当前模型类型 const { data: modelsData } = useSWR<{ models: DynamicModel[] }>( "/api/models", fetcher ); // 判断当前选中的模型类型 // 🆕 包含 gemini 和 huanke-gemini 两种 Gemini 图片生成 provider const isGeminiModel = modelsData?.models?.some( (m: DynamicModel) => m.id === selectedModelId && (m.providerType === "gemini" || m.providerType === "gemini-image" || m.providerType === "huanke-gemini") ) ?? false; const isQianwenVideoModel = modelsData?.models?.some( (m: DynamicModel) => m.id === selectedModelId && m.providerType === "qianwen-video" ) ?? 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 && m.providerType === "alibaba-image" ) ?? false; // 🆕 获取当前模型的显示名称 const currentModelDisplayName = modelsData?.models?.find( (m: DynamicModel) => m.id === selectedModelId )?.displayName; // 判断是否是视频生成模型(通过 providerType 或模型名称) const isVideoModel = modelsData?.models?.some( (m: DynamicModel) => m.id === selectedModelId && (m.providerType === "volcano-video" || m.modelId?.toLowerCase().includes("video") || m.modelId?.toLowerCase().includes("sora") || m.displayName?.includes("视频")) ) ?? false; // 控制等待动画延迟消失(Gemini 图片模型延迟 4 秒,其他模型 6 秒) const [showThinkingWithDelay, setShowThinkingWithDelay] = useState(false); useEffect(() => { const lastMessage = messages.length > 0 ? messages[messages.length - 1] : null; // 对于视频模型,检查是否有真正的视频内容(包含