File size: 4,843 Bytes
c59d808 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
import React, { useState, useEffect, useRef } from "react";
import { Send } from "lucide-react";
import MessageBubble from "./MessageBubble";
import { useChat } from "@ai-sdk/react";
import { TextStreamChatTransport } from "ai";
import { generateChatId, getTextMessagesFromParts } from "@/lib/chat";
import { CHAT_STATUS } from "@/constants/chat";
import { useRouter, useSearchParams } from "next/navigation";
import useChatStore from "@/store/chatStore";
import EmptyChat from "./EmptyChat";
const ChatInterface = () => {
const router = useRouter();
const searchParams = useSearchParams();
const chatParam = searchParams.get("chat");
const chatId = chatParam || generateChatId();
const initialMessages = useChatStore((state) => state.messages[chatId]);
const saveChat = useChatStore((state) => state.addMessages);
const [newMessage, setNewMessage] = useState("");
const messagesEndRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const [autoScroll, setAutoScroll] = useState(true);
const [isLoading, setIsLoading] = useState(true);
const { messages, sendMessage, status } = useChat({
id: chatId,
messages: initialMessages,
transport: new TextStreamChatTransport({
api: `${process.env.NEXT_PUBLIC_API_URL}/chat`,
}),
onFinish: (message) => {
saveChat(chatId, message.messages);
},
});
useEffect(() => {
if (autoScroll && containerRef.current) {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}
}, [messages, autoScroll]);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const handleScroll = () => {
const isAtBottom =
container.scrollTop + container.clientHeight >= container.scrollHeight;
setAutoScroll(isAtBottom);
};
container.addEventListener("scroll", handleScroll);
return () => container.removeEventListener("scroll", handleScroll);
}, []);
useEffect(() => {
if (newMessage.trim() === "") return;
if (chatId) {
router.replace(`?chat=${chatId}`);
}
}, [chatId, router, newMessage]);
const handleSendMessage = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (newMessage.trim() === "") return;
sendMessage({
text: newMessage,
});
setNewMessage("");
setAutoScroll(true);
};
const allMessages = getTextMessagesFromParts(messages);
useEffect(() => {
setIsLoading(false);
}, []);
return (
<div className="flex flex-col h-full bg-tertiary-100 rounded-b-xl">
<div
ref={containerRef}
className="flex-1 overflow-y-auto p-4 custom-scrollbar"
>
{!isLoading && allMessages.length === 0 && (
<div className="space-y-4 md:px-10 xl:px-30 h-full">
<EmptyChat onSuggestionClick={setNewMessage} />
</div>
)}
{allMessages.length > 0 && (
<div className="space-y-4 md:px-10 xl:px-30">
{allMessages.map((message) => (
<MessageBubble
key={message.id}
message={message}
isSender={message.isSender}
/>
))}
{status === CHAT_STATUS.SUBMITTED && (
<MessageBubble
message={{ text: "Thinking..." }}
isSender={false}
/>
)}
{status === CHAT_STATUS.ERROR && (
<MessageBubble
message={{ text: "An error occurred. Please try again later." }}
isSender={false}
/>
)}
<div ref={messagesEndRef} />
</div>
)}
</div>
<form onSubmit={handleSendMessage} className="pb-4 px-4 md:px-10 xl:px-30">
<div className="relative">
<input
type="text"
value={newMessage}
id="chat-input"
onChange={(e) => setNewMessage(e.target.value)}
className="block w-full p-6 md:ps-10 text-sm text-primary-700 border border-tertiary-200 bg-tertiary-200/10 rounded-xl focus:outline-primary-500 focus:ring-primary-500"
placeholder="Ask me anything about cooking, recipes, or ingredients..."
required
/>
<button
type="submit"
className="cursor-pointer text-grey-100 absolute end-3 bottom-3 bg-primary-600 enabled:hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-xl text-sm px-3 py-3 disabled:bg-primary-500 disabled:cursor-not-allowed"
disabled={
status === CHAT_STATUS.SUBMITTED || newMessage.trim() === ""
}
>
<Send size={18} />
</button>
</div>
</form>
</div>
);
};
export default ChatInterface;
|