"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 && (
)}
)}
);
}