fix(chat): Use a global chat context to avoid duplicate states

This commit is contained in:
iMADi-ARCH 2023-06-11 17:48:06 +05:30
parent 57f9ef6170
commit d276126d01
11 changed files with 81 additions and 59 deletions

View File

@ -0,0 +1,15 @@
"use client";
import { createContext } from "react";
import useChats from "./hooks/useChats";
import { ChatsState } from "./types";
export const ChatsContext = createContext<ChatsState | undefined>(undefined);
export const ChatsProvider = ({ children }: { children: React.ReactNode }) => {
const chatsState = useChats();
return (
<ChatsContext.Provider value={chatsState}>{children}</ChatsContext.Provider>
);
};

View File

@ -4,9 +4,9 @@ import { useAxios } from "@/lib/useAxios";
import { UUID } from "crypto";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { Chat, ChatMessage } from "../types";
import { Chat, ChatMessage } from "../../types";
export default function useChats(chatId?: UUID) {
export default function useChats() {
const [allChats, setAllChats] = useState<Chat[]>([]);
const [chat, setChat] = useState<Chat | null>(null);
const [isSendingMessage, setIsSendingMessage] = useState(false);
@ -60,6 +60,7 @@ export default function useChats(chatId?: UUID) {
}: {
options: Record<string, string | unknown>;
}) => {
fetchAllChats();
return axiosInstance.post<ChatResponse>(`/chat`, options);
};
@ -74,16 +75,8 @@ export default function useChats(chatId?: UUID) {
const sendMessage = async (chatId?: UUID, msg?: ChatMessage) => {
setIsSendingMessage(true);
// const chat_id = {
// ...((chatId || currentChatId) && {
// chat_id: chatId ?? currentChatId,
// }),
// };
if (msg) setMessage(msg);
const options: Record<string, unknown> = {
// ...(chat_id && { chat_id }),
// chat_id gets set only if either chatId or currentChatId exists, by the priority of chatId
chat_id: chatId,
model,
question: msg ? msg[1] : message[1],
@ -144,13 +137,13 @@ export default function useChats(chatId?: UUID) {
}
};
const resetChat = async () => {
setChat(null);
}
useEffect(() => {
fetchAllChats();
console.log(chatId);
if (chatId) {
fetchChat(chatId);
}
}, [fetchAllChats, fetchChat, chatId]);
}, [fetchAllChats]);
return {
allChats,
@ -158,6 +151,7 @@ export default function useChats(chatId?: UUID) {
isSendingMessage,
message,
setMessage,
resetChat,
fetchAllChats,
fetchChat,

View File

@ -0,0 +1,14 @@
import { useContext } from "react";
import { ChatsContext } from "../chats-provider";
const useChatsContext = () => {
const context = useContext(ChatsContext);
if (context === undefined) {
throw new Error("useChatsStore must be used inside ChatsProvider");
}
return context;
};
export default useChatsContext;

View File

@ -1,12 +1,12 @@
import { useEffect, useState } from "react";
import { isSpeechRecognitionSupported } from "../helpers/isSpeechRecognitionSupported";
import useChats from "./useChats";
import { isSpeechRecognitionSupported } from "../../helpers/isSpeechRecognitionSupported";
import useChatsContext from "./useChatsContext";
export const useSpeech = () => {
const [isListening, setIsListening] = useState(false);
const [speechSupported, setSpeechSupported] = useState(false);
const { setMessage } = useChats();
const { setMessage } = useChatsContext();
useEffect(() => {
if (isSpeechRecognitionSupported()) {

View File

@ -0,0 +1,3 @@
import useChats from "./hooks/useChats";
export type ChatsState = ReturnType<typeof useChats>;

View File

@ -1,19 +1,26 @@
"use client";
import { UUID } from "crypto";
import { useEffect } from "react";
import PageHeading from "../../components/ui/PageHeading";
import useChatsContext from "../ChatsProvider/hooks/useChatsContext";
import { ChatInput, ChatMessages } from "../components";
import useChats from "../hooks/useChats";
interface ChatPageProps {
params?: {
chatId?: UUID;
params: {
chatId: UUID;
};
}
export default function ChatPage({ params }: ChatPageProps) {
const chatId: UUID | undefined = params?.chatId;
const chatId: UUID | undefined = params.chatId;
const { chat, ...others } = useChats(chatId);
const { fetchChat, resetChat } = useChatsContext();
useEffect(() => {
// if (chatId)
if (!chatId) resetChat();
fetchChat(chatId);
}, [fetchChat, chatId]);
return (
<main className="flex flex-col w-full pt-10">
@ -24,9 +31,9 @@ export default function ChatPage({ params }: ChatPageProps) {
/>
<div className="relative h-full w-full flex flex-col flex-1 items-center">
<div className="h-full flex-1 w-full flex flex-col items-center">
{chat && <ChatMessages chat={chat} />}
<ChatMessages />
</div>
<ChatInput chatId={chatId} {...others} />
<ChatInput />
</div>
</section>
</main>

View File

@ -1,7 +1,7 @@
"use client";
import { MdMic, MdMicOff } from "react-icons/md";
import Button from "../../../../components/ui/Button";
import { useSpeech } from "../../../hooks/useSpeech";
import { useSpeech } from "../../../ChatsProvider/hooks/useSpeech";
export function MicButton() {
const { isListening, speechSupported, startListening } = useSpeech();

View File

@ -1,31 +1,17 @@
"use client";
import { ChatMessage } from "@/app/chat/types";
import { UUID } from "crypto";
import { Dispatch, SetStateAction } from "react";
import useChatsContext from "@/app/chat/ChatsProvider/hooks/useChatsContext";
import Button from "../../../../components/ui/Button";
import { ConfigButton } from "./ConfigButton";
import { MicButton } from "./MicButton";
interface ChatInputProps {
isSendingMessage: boolean;
sendMessage: (chatId?: UUID, msg?: ChatMessage) => Promise<void>;
setMessage: Dispatch<SetStateAction<ChatMessage>>;
message: ChatMessage;
chatId?: UUID;
}
export function ChatInput({
chatId,
isSendingMessage,
message,
sendMessage,
setMessage,
}: ChatInputProps) {
export function ChatInput() {
const { isSendingMessage, sendMessage, setMessage, message, chat } =
useChatsContext();
return (
<form
onSubmit={(e) => {
e.preventDefault();
if (!isSendingMessage) sendMessage(chatId);
if (!isSendingMessage) sendMessage(chat?.chatId);
}}
className="sticky bottom-0 p-5 bg-white dark:bg-black rounded-t-md border border-black/10 dark:border-white/25 border-b-0 w-full max-w-3xl flex items-center justify-center gap-2 z-20"
>
@ -36,7 +22,7 @@ export function ChatInput({
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault(); // Prevents the newline from being entered in the textarea
if (!isSendingMessage) sendMessage(chatId); // Call the submit function here
if (!isSendingMessage) sendMessage(chat?.chatId); // Call the submit function here
}
}}
className="w-full p-2 border border-gray-300 dark:border-gray-500 outline-none rounded dark:bg-gray-800"

View File

@ -1,22 +1,22 @@
"use client";
import Card from "@/app/components/ui/Card";
import { AnimatePresence } from "framer-motion";
import { useEffect, useRef } from "react";
import { Chat } from "../../types";
import { FC, useEffect, useRef } from "react";
import useChatsContext from "../../ChatsProvider/hooks/useChatsContext";
import ChatMessage from "./ChatMessage";
interface ChatMessagesProps {
chat: Chat;
}
export const ChatMessages = ({ chat }: ChatMessagesProps): JSX.Element => {
export const ChatMessages: FC = () => {
const lastChatRef = useRef<HTMLDivElement | null>(null);
const { chat } = useChatsContext();
useEffect(() => {
if (!chat) return;
if (chat.history.length > 2) {
lastChatRef.current?.scrollIntoView({ behavior: "auto", block: "start" });
}
}, [chat]);
if (!chat) return null;
return (
<Card className="p-5 max-w-3xl w-full flex flex-col h-full mb-8">

View File

@ -1,9 +1,9 @@
"use client";
import useChats from "../../hooks/useChats";
import useChatsContext from "../../ChatsProvider/hooks/useChatsContext";
import ChatsListItem from "./ChatsListItem";
import { NewChatButton } from "./NewChatButton";
export function ChatsList() {
const { allChats, deleteChat } = useChats();
const { allChats, deleteChat } = useChatsContext();
return (
<div className="sticky top-0 max-h-screen overflow-auto scrollbar">
<aside className="relative bg-white dark:bg-black max-w-xs w-full border-r border-black/10 dark:border-white/25 h-screen">

View File

@ -2,6 +2,7 @@
import { redirect } from "next/navigation";
import { FC, ReactNode } from "react";
import { useSupabase } from "../supabase-provider";
import { ChatsProvider } from "./ChatsProvider/chats-provider";
import { ChatsList } from "./components";
interface LayoutProps {
@ -13,10 +14,12 @@ const Layout: FC<LayoutProps> = ({ children }) => {
if (!session) redirect("/login");
return (
<div className="relative h-full w-full flex items-start">
<ChatsList />
{children}
</div>
<ChatsProvider>
<div className="relative h-full w-full flex items-start">
<ChatsList />
{children}
</div>
</ChatsProvider>
);
};