# 🎨 Lovable Clone - Frontend Templates > Complete React/Next.js component templates --- # 💬 I. CHAT INTERFACE COMPONENTS ## 1. Main Chat Panel **File: `apps/web/components/chat/chat-panel.tsx`** ```typescript 'use client'; import { useState, useRef, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { ScrollArea } from '@/components/ui/scroll-area'; import { ChatMessage } from './chat-message'; import { Send, Loader2 } from 'lucide-react'; import { useChatStore } from '@/stores/chat-store'; import { api } from '@/lib/api'; export function ChatPanel() { const [input, setInput] = useState(''); const [isStreaming, setIsStreaming] = useState(false); const scrollRef = useRef(null); const { messages, addMessage, projectId, conversationId } = useChatStore(); const scrollToBottom = () => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }; useEffect(() => { scrollToBottom(); }, [messages]); const sendMessage = async () => { if (!input.trim() || isStreaming) return; const userMessage = { id: Date.now().toString(), role: 'user' as const, content: input, timestamp: new Date() }; addMessage(userMessage); setInput(''); setIsStreaming(true); try { // Use EventSource for streaming const eventSource = new EventSource( `${process.env.NEXT_PUBLIC_API_URL}/api/chat/stream`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${localStorage.getItem('token')}` }, body: JSON.stringify({ message: input, projectId, conversationId }) } ); let assistantMessage = { id: (Date.now() + 1).toString(), role: 'assistant' as const, content: '', timestamp: new Date() }; addMessage(assistantMessage); eventSource.onmessage = (event) => { if (event.data === '[DONE]') { eventSource.close(); setIsStreaming(false); return; } const data = JSON.parse(event.data); if (data.token) { assistantMessage.content += data.token; // Update message in store useChatStore.getState().updateMessage(assistantMessage.id, { content: assistantMessage.content }); } }; eventSource.onerror = () => { eventSource.close(); setIsStreaming(false); console.error('Stream error'); }; } catch (error) { console.error('Send message error:', error); setIsStreaming(false); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }; return (
{/* Header */}

Chat

Describe what you want to build

{/* Messages */}
{messages.length === 0 ? (

Start a new conversation

Describe your app and I'll help you build it

) : ( messages.map((message) => ( )) )} {isStreaming && (
Thinking...
)}
{/* Input */}