| "use client"; |
|
|
| import React, { useState, useEffect, useRef } from 'react'; |
| import Image from 'next/image'; |
| import ChatMessage from './ChatMessage'; |
| import ChatInput from './ChatInput'; |
|
|
| type Message = { |
| role: 'user' | 'assistant'; |
| content: string; |
| }; |
|
|
| |
| const generateMeowCount = () => { |
| |
| return Math.floor(Math.random() * 30) + 1; |
| }; |
|
|
| const Chat: React.FC = () => { |
| const [messages, setMessages] = useState<Message[]>([ |
| { |
| role: 'assistant', |
| content: 'meow', |
| }, |
| ]); |
| const [isTyping, setIsTyping] = useState(false); |
| const [streamingContent, setStreamingContent] = useState(''); |
| const messagesEndRef = useRef<HTMLDivElement>(null); |
| const streamIntervalRef = useRef<NodeJS.Timeout | null>(null); |
|
|
| const scrollToBottom = () => { |
| messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); |
| }; |
|
|
| useEffect(() => { |
| scrollToBottom(); |
| }, [messages, isTyping, streamingContent]); |
|
|
| |
| useEffect(() => { |
| return () => { |
| if (streamIntervalRef.current) { |
| clearInterval(streamIntervalRef.current); |
| } |
| }; |
| }, []); |
|
|
| const handleSendMessage = (content: string) => { |
| |
| if (streamIntervalRef.current) { |
| clearInterval(streamIntervalRef.current); |
| streamIntervalRef.current = null; |
| } |
|
|
| |
| setMessages((prev) => [...prev, { role: 'user', content }]); |
| |
| |
| setIsTyping(true); |
| setStreamingContent(''); |
| |
| |
| setTimeout(() => { |
| const meowCount = generateMeowCount(); |
| let currentMeows = 0; |
| const meowArray = Array(meowCount).fill("meow"); |
| |
| |
| streamIntervalRef.current = setInterval(() => { |
| if (currentMeows < meowCount) { |
| currentMeows++; |
| setStreamingContent(meowArray.slice(0, currentMeows).join(" ")); |
| } else { |
| |
| if (streamIntervalRef.current) { |
| clearInterval(streamIntervalRef.current); |
| streamIntervalRef.current = null; |
| } |
| |
| |
| setMessages((prev) => [...prev, { |
| role: 'assistant', |
| content: meowArray.join(" ") |
| }]); |
| |
| setIsTyping(false); |
| setStreamingContent(''); |
| } |
| }, 100); |
| }, 500 + Math.random() * 500); |
| }; |
|
|
| return ( |
| <div className="flex flex-col h-full bg-white"> |
| <div className="flex-1 overflow-y-auto pb-4"> |
| <div> |
| {messages.map((message, index) => ( |
| <ChatMessage key={index} role={message.role} content={message.content} /> |
| ))} |
| {isTyping && ( |
| <div className="py-6 bg-white"> |
| <div className="max-w-3xl mx-auto flex items-start gap-4 px-4 sm:px-6 md:px-8"> |
| <div className="flex-shrink-0 w-8 h-8"> |
| <div className="w-8 h-8 flex items-center justify-center overflow-hidden"> |
| <Image src="/cat.png" alt="CatGPT Logo" width={32} height={32} /> |
| </div> |
| </div> |
| <div className="flex-1 min-w-0"> |
| <p className="font-medium text-sm mb-2 text-gray-800">CatGPT</p> |
| {streamingContent ? ( |
| <div className="prose max-w-none text-gray-800"> |
| <p className="whitespace-pre-wrap">{streamingContent}</p> |
| </div> |
| ) : ( |
| <div className="flex space-x-2 items-center"> |
| <span className="w-2 h-2 rounded-full bg-gray-400 animate-pulse"></span> |
| <span className="w-2 h-2 rounded-full bg-gray-400 animate-pulse" style={{ animationDelay: '0.2s' }}></span> |
| <span className="w-2 h-2 rounded-full bg-gray-400 animate-pulse" style={{ animationDelay: '0.4s' }}></span> |
| </div> |
| )} |
| </div> |
| </div> |
| </div> |
| )} |
| <div ref={messagesEndRef} /> |
| </div> |
| </div> |
| <div className="mt-auto"> |
| <ChatInput onSendMessage={handleSendMessage} disabled={isTyping} /> |
| </div> |
| </div> |
| ); |
| }; |
|
|
| export default Chat; |