mirror of
https://github.com/StanGirard/quivr.git
synced 2024-09-11 13:15:41 +03:00
use context to prevent having duplicate chat states (#297)
* fix(chat): Use a global chat context to avoid duplicate states * fix(chat): update chats list when creating a new chat
This commit is contained in:
parent
57f9ef6170
commit
52e723d534
15
frontend/app/chat/ChatsProvider/chats-provider.tsx
Normal file
15
frontend/app/chat/ChatsProvider/chats-provider.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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],
|
||||
@ -115,11 +108,11 @@ export default function useChats(chatId?: UUID) {
|
||||
};
|
||||
if (!chatId) {
|
||||
// Creating a new chat
|
||||
// setAllChats((chats) => {
|
||||
// console.log({ chats });
|
||||
// return [...chats, newChat];
|
||||
// });
|
||||
console.log("---- Creating a new chat ----");
|
||||
setAllChats((chats) => {
|
||||
console.log({ chats });
|
||||
return [...chats, newChat];
|
||||
});
|
||||
setChat(newChat);
|
||||
router.push(`/chat/${response.data.chatId}`);
|
||||
}
|
||||
@ -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,
|
14
frontend/app/chat/ChatsProvider/hooks/useChatsContext.tsx
Normal file
14
frontend/app/chat/ChatsProvider/hooks/useChatsContext.tsx
Normal 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;
|
@ -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()) {
|
3
frontend/app/chat/ChatsProvider/types.ts
Normal file
3
frontend/app/chat/ChatsProvider/types.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import useChats from "./hooks/useChats";
|
||||
|
||||
export type ChatsState = ReturnType<typeof useChats>;
|
@ -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>
|
||||
|
@ -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();
|
||||
|
@ -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"
|
||||
|
@ -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">
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user