import { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { useChatStore, Message } from '../store/chatStore'; import { useGatewayStore } from '../store/gatewayStore'; import { Paperclip, ChevronDown, Terminal, SquarePen, ArrowUp, MessageSquare, Download, Copy, Check } from 'lucide-react'; import { Button, EmptyState } from './ui'; import { listItemVariants, defaultTransition, fadeInVariants } from '../lib/animations'; import { FirstConversationPrompt } from './FirstConversationPrompt'; import { MessageSearch } from './MessageSearch'; export function ChatArea() { const { messages, currentAgent, isStreaming, currentModel, sendMessage: sendToGateway, setCurrentModel, initStreamListener, newConversation, } = useChatStore(); const { connectionState, clones, models } = useGatewayStore(); const [input, setInput] = useState(''); const [showModelPicker, setShowModelPicker] = useState(false); const scrollRef = useRef(null); const textareaRef = useRef(null); const messageRefs = useRef>(new Map()); // Get current clone for first conversation prompt const currentClone = useMemo(() => { if (!currentAgent) return null; return clones.find((c) => c.id === currentAgent.id) || null; }, [currentAgent, clones]); // Check if should show first conversation prompt const showFirstPrompt = messages.length === 0 && currentClone && !currentClone.onboardingCompleted; // Handle suggestion click from first conversation prompt const handleSelectSuggestion = (text: string) => { setInput(text); textareaRef.current?.focus(); }; // Auto-resize textarea const adjustTextarea = useCallback(() => { const el = textareaRef.current; if (el) { el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, 160) + 'px'; } }, []); // Init agent stream listener on mount useEffect(() => { const unsub = initStreamListener(); return unsub; }, []); // Auto-scroll to bottom on new messages useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }, [messages]); const handleSend = () => { if (!input.trim() || isStreaming || !connected) return; sendToGateway(input); setInput(''); }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } }; const connected = connectionState === 'connected'; // Navigate to a specific message by ID const handleNavigateToMessage = useCallback((messageId: string) => { const messageEl = messageRefs.current.get(messageId); if (messageEl && scrollRef.current) { messageEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); // Add highlight effect messageEl.classList.add('ring-2', 'ring-orange-400', 'ring-offset-2'); setTimeout(() => { messageEl.classList.remove('ring-2', 'ring-orange-400', 'ring-offset-2'); }, 2000); } }, []); return (
{/* Header */}

{currentAgent?.name || 'ZCLAW'}

{isStreaming ? ( 正在输入中 ) : ( {connected ? 'Gateway 已连接' : 'Gateway 未连接'} )}
{messages.length > 0 && ( )} {messages.length > 0 && ( )}
{/* Messages */}
{messages.length === 0 && ( {showFirstPrompt && currentClone ? ( ) : ( } title="欢迎使用 ZCLAW" description={connected ? '发送消息开始对话' : '请先在设置中连接 Gateway'} /> )} )} {messages.map((message) => ( { if (el) messageRefs.current.set(message.id, el); }} variants={listItemVariants} initial="hidden" animate="visible" layout transition={defaultTransition} > ))}
{/* Input */}