"use client"; import { useState, useCallback, useMemo } from "react"; import { motion, AnimatePresence } from "framer-motion"; import type { ChatMessage, ImageAsset } from "@/lib/types"; import { ImageGrid } from "./image-grid"; import { messageEnter, assistantEnter } from "@/components/ui/motion-presets"; function StreamingLines({ text }: { text: string }) { const lines = useMemo(() => text.split("\n"), [text]); const total = lines.length; return (
{lines.map((line, i) => { const fromEnd = total - 1 - i; const opacity = fromEnd <= 0 ? 1 : fromEnd === 1 ? 0.85 : fromEnd === 2 ? 0.7 : 0.6; return ( {line} {i < total - 1 && "\n"} ); })}
); } function CopyButton({ text }: { text: string }) { const [copied, setCopied] = useState(false); const handleCopy = useCallback(async () => { try { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 1500); } catch { // fallback } }, [text]); return ( ); } interface ChatMessagesProps { messages: ChatMessage[]; isLoading: boolean; streamingText: string; streamingImages: ImageAsset[]; statusText: string; } export function ChatMessages({ messages, isLoading, streamingText, streamingImages, statusText, }: ChatMessagesProps) { return (
{messages.map((msg) => (
{/* 多图参考(新格式) */} {msg.refImageUrls && msg.refImageUrls.length > 0 && (
{msg.refImageUrls.map((url, i) => ( {`参考图 ))}
参考图({msg.refImageUrls.length} 张)
)} {/* 兼容旧数据:单图 */} {!msg.refImageUrls && msg.refImageUrl && (
参考图 参考图
)}
{msg.content}
{msg.images && msg.images.length > 0 && ( <> {msg.modelName && (
由 {msg.modelName} 生成
)} )}
{msg.content && (
)}
))} {/* AI 正在回复(流式) */} {isLoading && (
{statusText && (
{statusText}
)} {streamingText && ( )} {streamingImages.length > 0 && } {!streamingText && !statusText && (
)}
)}
); }