mirror of
https://github.com/StanGirard/quivr.git
synced 2024-10-26 14:00:37 +03:00
Merge branch 'feat/user-chat-history' of github.com:StanGirard/Quivr into feat/user-chat-history
This commit is contained in:
commit
c734202eca
@ -2,8 +2,7 @@ import os
|
||||
|
||||
import pypandoc
|
||||
from auth.auth_bearer import JWTBearer
|
||||
from fastapi import Depends, FastAPI
|
||||
from llm.summarization import llm_evaluate_summaries
|
||||
from fastapi import FastAPI
|
||||
from logger import get_logger
|
||||
from middlewares.cors import add_cors_middleware
|
||||
from models.chats import ChatMessage
|
||||
|
@ -4,7 +4,7 @@ from tempfile import SpooledTemporaryFile
|
||||
|
||||
from auth.auth_bearer import JWTBearer
|
||||
from crawl.crawler import CrawlWebsite
|
||||
from fastapi import APIRouter, Depends, UploadFile
|
||||
from fastapi import APIRouter, Depends, Request, UploadFile
|
||||
from middlewares.cors import add_cors_middleware
|
||||
from models.users import User
|
||||
from parsers.github import process_github
|
||||
@ -15,9 +15,11 @@ from utils.vectors import CommonsDep
|
||||
crawl_router = APIRouter()
|
||||
|
||||
@crawl_router.post("/crawl/", dependencies=[Depends(JWTBearer())])
|
||||
async def crawl_endpoint(commons: CommonsDep, crawl_website: CrawlWebsite, enable_summarization: bool = False, credentials: dict = Depends(JWTBearer())):
|
||||
async def crawl_endpoint(request: Request,commons: CommonsDep, crawl_website: CrawlWebsite, enable_summarization: bool = False, credentials: dict = Depends(JWTBearer())):
|
||||
max_brain_size = os.getenv("MAX_BRAIN_SIZE")
|
||||
|
||||
if request.headers.get('Openai-Api-Key'):
|
||||
max_brain_size = os.getenv("MAX_BRAIN_SIZE_WITH_KEY",209715200)
|
||||
|
||||
user = User(email=credentials.get('email', 'none'))
|
||||
user_vectors_response = commons['supabase'].table("vectors").select(
|
||||
"name:metadata->>file_name, size:metadata->>file_size", count="exact") \
|
||||
|
@ -3,14 +3,14 @@ import os
|
||||
import time
|
||||
|
||||
from auth.auth_bearer import JWTBearer
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from models.users import User
|
||||
from utils.vectors import CommonsDep
|
||||
|
||||
user_router = APIRouter()
|
||||
|
||||
max_brain_size_with_own_key = os.getenv("MAX_BRAIN_SIZE_WITH_KEY",209715200)
|
||||
@user_router.get("/user", dependencies=[Depends(JWTBearer())])
|
||||
async def get_user_endpoint(commons: CommonsDep, credentials: dict = Depends(JWTBearer())):
|
||||
async def get_user_endpoint(request: Request,commons: CommonsDep, credentials: dict = Depends(JWTBearer())):
|
||||
|
||||
# Create a function that returns the unique documents out of the vectors
|
||||
# Create a function that returns the list of documents that can take in what to put in the select + the filter
|
||||
@ -27,11 +27,17 @@ async def get_user_endpoint(commons: CommonsDep, credentials: dict = Depends(JWT
|
||||
current_brain_size = sum(float(doc['size']) for doc in user_unique_vectors)
|
||||
|
||||
max_brain_size = os.getenv("MAX_BRAIN_SIZE")
|
||||
if request.headers.get('Openai-Api-Key'):
|
||||
max_brain_size = max_brain_size_with_own_key
|
||||
|
||||
# Create function get user request stats -> nombre de requetes par jour + max number of requests -> svg to display the number of requests ? une fusee ?
|
||||
user = User(email=credentials.get('email', 'none'))
|
||||
date = time.strftime("%Y%m%d")
|
||||
max_requests_number = os.getenv("MAX_REQUESTS_NUMBER")
|
||||
|
||||
if request.headers.get('Openai-Api-Key'):
|
||||
max_brain_size = max_brain_size_with_own_key
|
||||
|
||||
requests_stats = commons['supabase'].from_('users').select(
|
||||
'*').filter("email", "eq", user.email).execute()
|
||||
|
||||
|
30
frontend/app/chat/[...chatId]/page.tsx
Normal file
30
frontend/app/chat/[...chatId]/page.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
"use client";
|
||||
import { UUID } from "crypto";
|
||||
import PageHeading from "../../components/ui/PageHeading";
|
||||
import { ChatInput, ChatMessages } from "../components";
|
||||
import useChats from "../hooks/useChats";
|
||||
|
||||
interface ChatPageProps {
|
||||
params?: {
|
||||
chatId?: UUID;
|
||||
};
|
||||
}
|
||||
|
||||
export default function ChatPage({ params }: ChatPageProps) {
|
||||
const chatId: UUID | undefined = params?.chatId;
|
||||
|
||||
const { chat, ...others } = useChats(chatId);
|
||||
|
||||
return (
|
||||
<main className="flex flex-col w-full">
|
||||
<section className="flex flex-col items-center w-full overflow-auto">
|
||||
<PageHeading
|
||||
title="Chat with your brain"
|
||||
subtitle="Talk to a language model about your uploaded data"
|
||||
/>
|
||||
{chat && <ChatMessages chat={chat} />}
|
||||
<ChatInput chatId={chatId} {...others} />
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
"use client";
|
||||
import Link from "next/link";
|
||||
import { MdSettings } from "react-icons/md";
|
||||
import Button from "../../../../components/ui/Button";
|
||||
|
||||
export function ConfigButton() {
|
||||
return (
|
||||
<Link href={"/config"}>
|
||||
<Button className="px-3" variant={"tertiary"}>
|
||||
<MdSettings className="text-2xl" />
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
"use client";
|
||||
import { MdMic, MdMicOff } from "react-icons/md";
|
||||
import Button from "../../../../components/ui/Button";
|
||||
import { useSpeech } from "../../../hooks/useSpeech";
|
||||
|
||||
export function MicButton() {
|
||||
const { isListening, speechSupported, startListening } = useSpeech();
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="px-3"
|
||||
variant={"tertiary"}
|
||||
type="button"
|
||||
onClick={startListening}
|
||||
disabled={!speechSupported}
|
||||
>
|
||||
{isListening ? (
|
||||
<MdMicOff className="text-2xl" />
|
||||
) : (
|
||||
<MdMic className="text-2xl" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
"use client";
|
||||
import { ChatMessage } from "@/app/chat/types";
|
||||
import { UUID } from "crypto";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
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) {
|
||||
return (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
if (!isSendingMessage) sendMessage(chatId);
|
||||
}}
|
||||
className="fixed p-5 bg-white dark:bg-black rounded-t-md border border-black/10 dark:border-white/25 bottom-0 w-full max-w-3xl flex items-center justify-center gap-2 z-20"
|
||||
>
|
||||
<textarea
|
||||
autoFocus
|
||||
value={message[1]}
|
||||
onChange={(e) => setMessage((msg) => [msg[0], e.target.value])}
|
||||
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
|
||||
}
|
||||
}}
|
||||
className="w-full p-2 border border-gray-300 dark:border-gray-500 outline-none rounded dark:bg-gray-800"
|
||||
placeholder="Begin conversation here..."
|
||||
/>
|
||||
<Button type="submit" isLoading={isSendingMessage}>
|
||||
{isSendingMessage ? "Thinking..." : "Chat"}
|
||||
</Button>
|
||||
<MicButton />
|
||||
<ConfigButton />
|
||||
</form>
|
||||
);
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
import { motion } from "framer-motion"
|
||||
import { forwardRef, Ref } from "react"
|
||||
import ReactMarkdown from "react-markdown"
|
||||
"use client";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import { forwardRef, Ref } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
|
||||
const ChatMessage = forwardRef(
|
||||
(
|
||||
@ -9,8 +10,8 @@ const ChatMessage = forwardRef(
|
||||
speaker,
|
||||
text,
|
||||
}: {
|
||||
speaker: string
|
||||
text: string
|
||||
speaker: string;
|
||||
text: string;
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
@ -26,28 +27,32 @@ const ChatMessage = forwardRef(
|
||||
exit={{ y: -24, opacity: 0 }}
|
||||
className={cn(
|
||||
"py-3 px-3 md:px-6 w-full dark:border-white/25 flex flex-col max-w-4xl overflow-hidden scroll-pt-32",
|
||||
speaker === "user" ? "" : "bg-gray-200 dark:bg-gray-800 bg-opacity-60 py-8",
|
||||
speaker === "user"
|
||||
? ""
|
||||
: "bg-gray-200 dark:bg-gray-800 bg-opacity-60 py-8"
|
||||
)}
|
||||
style={speaker === "user" ? { whiteSpace: "pre-line" } : {}} // Add this line to preserve line breaks
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"capitalize text-xs bg-sky-200 rounded-xl p-1 px-2 mb-2 w-fit dark:bg-sky-700"
|
||||
)}>
|
||||
)}
|
||||
>
|
||||
{speaker}
|
||||
</span>
|
||||
<>
|
||||
<ReactMarkdown
|
||||
// remarkRehypeOptions={{}}
|
||||
className="prose dark:prose-invert ml-[6px] mt-1">
|
||||
className="prose dark:prose-invert ml-[6px] mt-1"
|
||||
>
|
||||
{text}
|
||||
</ReactMarkdown>
|
||||
</>
|
||||
</motion.div>
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
ChatMessage.displayName = "ChatMessage"
|
||||
ChatMessage.displayName = "ChatMessage";
|
||||
|
||||
export default ChatMessage
|
||||
export default ChatMessage;
|
44
frontend/app/chat/components/ChatMessages/index.tsx
Normal file
44
frontend/app/chat/components/ChatMessages/index.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
"use client";
|
||||
import Card from "@/app/components/ui/Card";
|
||||
import { AnimatePresence } from "framer-motion";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Chat } from "../../types";
|
||||
import ChatMessage from "./ChatMessage";
|
||||
|
||||
interface ChatMessagesProps {
|
||||
chat: Chat;
|
||||
}
|
||||
|
||||
export const ChatMessages = ({ chat }: ChatMessagesProps): JSX.Element => {
|
||||
const lastChatRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
lastChatRef.current?.scrollIntoView({ behavior: "auto", block: "start" });
|
||||
}, [chat]);
|
||||
|
||||
return (
|
||||
<Card className="p-5 max-w-3xl w-full flex-1 flex flex-col mb-8">
|
||||
<div className="">
|
||||
{history.length === 0 ? (
|
||||
<div className="text-center opacity-50">
|
||||
Ask a question, or describe a task.
|
||||
</div>
|
||||
) : (
|
||||
<AnimatePresence initial={false}>
|
||||
{chat.history.map(([speaker, text], idx) => {
|
||||
return (
|
||||
<ChatMessage
|
||||
ref={idx === history.length - 1 ? lastChatRef : null}
|
||||
key={idx}
|
||||
speaker={speaker}
|
||||
text={text}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</AnimatePresence>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
export default ChatMessages;
|
61
frontend/app/chat/components/ChatsList/ChatsListItem.tsx
Normal file
61
frontend/app/chat/components/ChatsList/ChatsListItem.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { UUID } from "crypto";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { FC } from "react";
|
||||
import { FiTrash2 } from "react-icons/fi";
|
||||
import { MdChatBubbleOutline } from "react-icons/md";
|
||||
import { Chat } from "../../types";
|
||||
|
||||
interface ChatsListItemProps {
|
||||
chat: Chat;
|
||||
deleteChat: (id: UUID) => void;
|
||||
}
|
||||
|
||||
const ChatsListItem: FC<ChatsListItemProps> = ({ chat, deleteChat }) => {
|
||||
const pathname = usePathname()?.split("/").at(-1);
|
||||
const selected = chat.chatId === pathname;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"w-full border-b border-black/10 dark:border-white/25 last:border-none relative group flex overflow-x-hidden hover:bg-gray-100 dark:hover:bg-gray-800",
|
||||
selected ? "bg-gray-100 text-primary" : ""
|
||||
)}
|
||||
>
|
||||
<Link
|
||||
className="flex flex-col flex-1 min-w-0 p-4"
|
||||
href={`/chat/${chat.chatId}`}
|
||||
key={chat.chatId}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<MdChatBubbleOutline className="text-xl" />
|
||||
|
||||
<p className="min-w-0 flex-1 whitespace-nowrap">
|
||||
{chat.history[chat.history.length - 1][1]}
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid-cols-2 text-xs opacity-50 whitespace-nowrap">
|
||||
{chat.chatId}
|
||||
</div>
|
||||
</Link>
|
||||
<div className="opacity-0 group-hover:opacity-100 flex items-center justify-center hover:text-red-700 bg-gradient-to-l from-white dark:from-black to-transparent z-10 transition-opacity">
|
||||
<button
|
||||
className="p-5"
|
||||
type="button"
|
||||
onClick={() => deleteChat(chat.chatId)}
|
||||
>
|
||||
<FiTrash2 />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Fade to white */}
|
||||
<div
|
||||
aria-hidden
|
||||
className="not-sr-only absolute left-1/2 top-0 bottom-0 right-0 bg-gradient-to-r from-transparent to-white dark:to-black pointer-events-none"
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatsListItem;
|
11
frontend/app/chat/components/ChatsList/NewChatButton.tsx
Normal file
11
frontend/app/chat/components/ChatsList/NewChatButton.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import Link from "next/link";
|
||||
import { BsPlusSquare } from "react-icons/bs";
|
||||
|
||||
export const NewChatButton = () => (
|
||||
<Link
|
||||
href="/chat"
|
||||
className="px-4 py-2 mx-4 my-2 border border-primary hover:text-white hover:bg-primary shadow-lg rounded-lg flex items-center justify-center"
|
||||
>
|
||||
<BsPlusSquare className="h-6 w-6 mr-2" /> New Chat
|
||||
</Link>
|
||||
);
|
21
frontend/app/chat/components/ChatsList/index.tsx
Normal file
21
frontend/app/chat/components/ChatsList/index.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
"use client";
|
||||
import useChats from "../../hooks/useChats";
|
||||
import ChatsListItem from "./ChatsListItem";
|
||||
import { NewChatButton } from "./NewChatButton";
|
||||
export function ChatsList() {
|
||||
const { allChats, deleteChat } = useChats();
|
||||
return (
|
||||
<aside className="h-screen bg-white dark:bg-black max-w-xs w-full border-r border-black/10 dark:border-white/25 ">
|
||||
<NewChatButton />
|
||||
<div className="flex flex-col gap-0">
|
||||
{allChats.map((chat) => (
|
||||
<ChatsListItem
|
||||
key={chat.chatId}
|
||||
chat={chat}
|
||||
deleteChat={deleteChat}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
export * from "./ChatInput";
|
||||
export * from "./ChatMessage";
|
||||
export * from "./ChatMessages";
|
||||
export * from "./ChatMessages/ChatInput";
|
||||
export * from "./ChatMessages/ChatMessage";
|
||||
export * from "./ChatsList";
|
||||
|
164
frontend/app/chat/hooks/useChats.ts
Normal file
164
frontend/app/chat/hooks/useChats.ts
Normal file
@ -0,0 +1,164 @@
|
||||
import { useBrainConfig } from "@/lib/context/BrainConfigProvider/hooks/useBrainConfig";
|
||||
import { useToast } from "@/lib/hooks/useToast";
|
||||
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";
|
||||
|
||||
export default function useChats(chatId?: UUID) {
|
||||
const [allChats, setAllChats] = useState<Chat[]>([]);
|
||||
const [chat, setChat] = useState<Chat | null>(null);
|
||||
const [currentChatId, setCurrentChatId] = useState<UUID | undefined>(chatId);
|
||||
const [isSendingMessage, setIsSendingMessage] = useState(false);
|
||||
const [message, setMessage] = useState<ChatMessage>(["", ""]); // for optimistic updates
|
||||
|
||||
const { axiosInstance } = useAxios();
|
||||
const {
|
||||
config: { maxTokens, model, temperature },
|
||||
} = useBrainConfig();
|
||||
const router = useRouter();
|
||||
const { publish } = useToast();
|
||||
|
||||
const fetchAllChats = useCallback(async () => {
|
||||
try {
|
||||
console.log("Fetching all chats");
|
||||
const response = await axiosInstance.get<{
|
||||
chats: Chat[];
|
||||
}>(`/chat`);
|
||||
setAllChats(response.data.chats);
|
||||
console.log("Fetched all chats");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: "Error occured while fetching your chats",
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchChat = useCallback(async (chatId?: UUID) => {
|
||||
console.log(currentChatId, chatId);
|
||||
|
||||
if (!currentChatId && !chatId) throw new Error("No ID provided");
|
||||
setCurrentChatId(chatId);
|
||||
try {
|
||||
console.log(`Fetching chat ${chatId ?? currentChatId}`);
|
||||
const response = await axiosInstance.get<Chat>(
|
||||
`/chat/${chatId ?? currentChatId}`
|
||||
);
|
||||
setChat(response.data);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: `Error occured while fetching ${chatId}`,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const sendMessage = async (chatId?: UUID, msg?: ChatMessage) => {
|
||||
setIsSendingMessage(true);
|
||||
|
||||
// const chat_id = {
|
||||
// ...((chatId || currentChatId) && {
|
||||
// chat_id: chatId ?? currentChatId,
|
||||
// }),
|
||||
// };
|
||||
|
||||
if (msg) setMessage(msg);
|
||||
const options = {
|
||||
// ...(chat_id && { chat_id }),
|
||||
// chat_id gets set only if either chatId or currentChatId exists, by the priority of chatId
|
||||
chat_id: chatId
|
||||
? chatId[0]
|
||||
: currentChatId
|
||||
? currentChatId[0]
|
||||
: undefined,
|
||||
model,
|
||||
question: msg ? msg[1] : message[1],
|
||||
history: chat ? chat.history : [],
|
||||
temperature,
|
||||
max_tokens: maxTokens,
|
||||
use_summarization: false,
|
||||
};
|
||||
|
||||
console.log({ options });
|
||||
|
||||
const response = await axiosInstance.post<
|
||||
// response.data.chatId can be undefined when the max number of requests has reached
|
||||
Omit<Chat, "chatId"> & { chatId: UUID | undefined }
|
||||
>(`/chat`, options);
|
||||
|
||||
// response.data.chatId can be undefined when the max number of requests has reached
|
||||
if (!response.data.chatId) {
|
||||
publish({
|
||||
text: "You have reached max number of requests.",
|
||||
variant: "danger",
|
||||
});
|
||||
setMessage(["", ""]);
|
||||
setIsSendingMessage(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const newChat = {
|
||||
chatId: response.data.chatId,
|
||||
history: response.data.history,
|
||||
};
|
||||
if (!chatId) {
|
||||
// Creating a new chat
|
||||
// setAllChats((chats) => {
|
||||
// console.log({ chats });
|
||||
// return [...chats, newChat];
|
||||
// });
|
||||
fetchAllChats();
|
||||
setCurrentChatId(response.data.chatId);
|
||||
setChat(newChat);
|
||||
router.push(`/chat/${response.data.chatId}`);
|
||||
}
|
||||
setChat(newChat);
|
||||
setMessage(["", ""]);
|
||||
setIsSendingMessage(false);
|
||||
};
|
||||
|
||||
const deleteChat = async (chatId: UUID) => {
|
||||
try {
|
||||
await axiosInstance.delete(`/chat/${chatId}`);
|
||||
setAllChats((chats) => chats.filter((chat) => chat.chatId !== chatId));
|
||||
// TODO: Change route only when the current chat is being deleted
|
||||
console.log({ chatIdsaldkfj: chat?.chatId, currentChatId, chatId });
|
||||
router.push("/chat");
|
||||
publish({
|
||||
text: `Chat sucessfully deleted. Id: ${chatId}`,
|
||||
variant: "success",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error deleting chat:", error);
|
||||
publish({ text: `Error deleting chat: ${error}`, variant: "danger" });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchAllChats();
|
||||
console.log(chatId);
|
||||
if (chatId) {
|
||||
setCurrentChatId(chatId);
|
||||
fetchChat(chatId);
|
||||
}
|
||||
}, [fetchAllChats, fetchChat, chatId]);
|
||||
|
||||
return {
|
||||
allChats,
|
||||
chat,
|
||||
currentChatId,
|
||||
isSendingMessage,
|
||||
message,
|
||||
setMessage,
|
||||
|
||||
fetchAllChats,
|
||||
fetchChat,
|
||||
|
||||
deleteChat,
|
||||
sendMessage,
|
||||
};
|
||||
}
|
25
frontend/app/chat/layout.tsx
Normal file
25
frontend/app/chat/layout.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
"use client";
|
||||
import { redirect } from "next/navigation";
|
||||
import { FC, ReactNode } from "react";
|
||||
import { useSupabase } from "../supabase-provider";
|
||||
import { ChatsList } from "./components";
|
||||
|
||||
interface LayoutProps {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
const Layout: FC<LayoutProps> = ({ children }) => {
|
||||
const { session } = useSupabase();
|
||||
if (!session) redirect("/login");
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full flex pt-20">
|
||||
<div className="h-full">
|
||||
<ChatsList />
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
@ -3,5 +3,8 @@ import { UUID } from "crypto";
|
||||
export interface Chat {
|
||||
chatId: UUID;
|
||||
chatName: string;
|
||||
history: Array<[string, string]>;
|
||||
history: ChatHistory;
|
||||
}
|
||||
export type ChatMessage = [string, string];
|
||||
|
||||
export type ChatHistory = ChatMessage[];
|
||||
|
@ -55,7 +55,7 @@ export const NavItems: FC<NavItemsProps> = ({
|
||||
{isUserLoggedIn && (
|
||||
<>
|
||||
<Link aria-label="account" className="" href={"/user"}>
|
||||
<MdPerson />
|
||||
<MdPerson className="text-2xl" />
|
||||
</Link>
|
||||
<Link href={"/config"}>
|
||||
<Button
|
||||
|
Loading…
Reference in New Issue
Block a user