fix: fix some bugs (#1201)

* feat(brainSettings): add feed process instrcution to knowledge tab

* feat: prevent default brain auto fetch

* feat: prevent chat submision on submit button click

* feat: remove unrelevant toast

* feat: remove duplicated GA initialization

* feat: add brain name in notifications

* fix(test): update analytics import path

* refactor: move ChatsList utils to ChatsList directory

* fix(test): update chatlist tests
This commit is contained in:
Mamadou DICKO 2023-09-18 21:28:07 +02:00 committed by GitHub
parent 8914c7c357
commit 1ec736b357
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 222 additions and 255 deletions

View File

@ -49,6 +49,7 @@ else:
@celery.task(name="process_file_and_notify") @celery.task(name="process_file_and_notify")
def process_file_and_notify( def process_file_and_notify(
file_name: str, file_name: str,
file_original_name: str,
enable_summarization, enable_summarization,
brain_id, brain_id,
openai_api_key, openai_api_key,
@ -77,6 +78,7 @@ def process_file_and_notify(
enable_summarization=enable_summarization, enable_summarization=enable_summarization,
brain_id=brain_id, brain_id=brain_id,
openai_api_key=openai_api_key, openai_api_key=openai_api_key,
original_file_name=file_original_name,
) )
) )
@ -129,6 +131,7 @@ def process_crawl_and_notify(
enable_summarization=enable_summarization, enable_summarization=enable_summarization,
brain_id=brain_id, brain_id=brain_id,
openai_api_key=openai_api_key, openai_api_key=openai_api_key,
original_file_name=crawl_website_url,
) )
) )
else: else:

View File

@ -4,23 +4,21 @@ from uuid import UUID
from auth import AuthBearer, get_current_user from auth import AuthBearer, get_current_user
from celery_worker import process_file_and_notify from celery_worker import process_file_and_notify
from fastapi import APIRouter, Depends, Query, Request, UploadFile from fastapi import APIRouter, Depends, Query, Request, UploadFile
from models import Brain, File, UserIdentity, UserUsage from models import Brain, UserIdentity, UserUsage
from models.databases.supabase.notifications import ( from models.databases.supabase.notifications import (
CreateNotificationProperties, CreateNotificationProperties,
NotificationUpdatableProperties,
) )
from models.notifications import NotificationsStatusEnum from models.notifications import NotificationsStatusEnum
from repository.brain import get_brain_details from repository.brain import get_brain_details
from repository.files.upload_file import upload_file_storage from repository.files.upload_file import upload_file_storage
from repository.notification.add_notification import add_notification from repository.notification.add_notification import add_notification
from repository.notification.update_notification import update_notification_by_id
from repository.user_identity import get_user_identity from repository.user_identity import get_user_identity
from utils.file import convert_bytes, get_file_size
from routes.authorizations.brain_authorization import ( from routes.authorizations.brain_authorization import (
RoleEnum, RoleEnum,
validate_brain_authorization, validate_brain_authorization,
) )
from utils.file import convert_bytes, get_file_size
from utils.processors import filter_file
upload_router = APIRouter() upload_router = APIRouter()
@ -86,6 +84,7 @@ async def upload_file(
upload_file_storage(file_content, filename_with_brain_id) upload_file_storage(file_content, filename_with_brain_id)
process_file_and_notify.delay( process_file_and_notify.delay(
file_name=filename_with_brain_id, file_name=filename_with_brain_id,
file_original_name=uploadFile.filename,
enable_summarization=enable_summarization, enable_summarization=enable_summarization,
brain_id=brain_id, brain_id=brain_id,
openai_api_key=openai_api_key, openai_api_key=openai_api_key,

View File

@ -1,6 +1,7 @@
from models.brains import Brain from models.brains import Brain
from models.files import File from models.files import File
from parsers.audio import process_audio from parsers.audio import process_audio
from parsers.code_python import process_python
from parsers.csv import process_csv from parsers.csv import process_csv
from parsers.docx import process_docx from parsers.docx import process_docx
from parsers.epub import process_epub from parsers.epub import process_epub
@ -12,7 +13,7 @@ from parsers.pdf import process_pdf
from parsers.powerpoint import process_powerpoint from parsers.powerpoint import process_powerpoint
from parsers.txt import process_txt from parsers.txt import process_txt
from parsers.xlsx import process_xlsx from parsers.xlsx import process_xlsx
from parsers.code_python import process_python from repository.brain.get_brain_by_id import get_brain_by_id
file_processors = { file_processors = {
".txt": process_txt, ".txt": process_txt,
@ -48,27 +49,32 @@ async def filter_file(
enable_summarization: bool, enable_summarization: bool,
brain_id, brain_id,
openai_api_key, openai_api_key,
original_file_name=None,
): ):
await file.compute_file_sha1() await file.compute_file_sha1()
print("file sha1", file.file_sha1)
file_exists = file.file_already_exists() file_exists = file.file_already_exists()
file_exists_in_brain = file.file_already_exists_in_brain(brain_id) file_exists_in_brain = file.file_already_exists_in_brain(brain_id)
using_file_name = original_file_name or file.file.filename if file.file else ""
brain = get_brain_by_id(brain_id)
if brain is None:
raise Exception("It seems like you're uploading knowledge to an unknown brain.")
if file_exists_in_brain: if file_exists_in_brain:
return create_response( return create_response(
f"🤔 {file.file.filename} already exists in brain {brain_id}.", # pyright: ignore reportPrivateUsage=none f"🤔 {using_file_name} already exists in brain {brain.name}.", # pyright: ignore reportPrivateUsage=none
"warning", "warning",
) )
elif file.file_is_empty(): elif file.file_is_empty():
return create_response( return create_response(
f"{file.file.filename} is empty.", # pyright: ignore reportPrivateUsage=none f"{original_file_name} is empty.", # pyright: ignore reportPrivateUsage=none
"error", # pyright: ignore reportPrivateUsage=none "error", # pyright: ignore reportPrivateUsage=none
) )
elif file_exists: elif file_exists:
file.link_file_to_brain(brain=Brain(id=brain_id)) file.link_file_to_brain(brain=Brain(id=brain_id))
return create_response( return create_response(
f"{file.file.filename} has been uploaded to brain {brain_id}.", # pyright: ignore reportPrivateUsage=none f"{using_file_name} has been uploaded to brain {brain.name}.", # pyright: ignore reportPrivateUsage=none
"success", "success",
) )
@ -81,18 +87,18 @@ async def filter_file(
user_openai_api_key=openai_api_key, user_openai_api_key=openai_api_key,
) )
return create_response( return create_response(
f"{file.file.filename} has been uploaded to brain {brain_id}.", # pyright: ignore reportPrivateUsage=none f"{using_file_name} has been uploaded to brain {brain.name}.", # pyright: ignore reportPrivateUsage=none
"success", "success",
) )
except Exception as e: except Exception as e:
# Add more specific exceptions as needed. # Add more specific exceptions as needed.
print(f"Error processing file: {e}") print(f"Error processing file: {e}")
return create_response( return create_response(
f"⚠️ An error occurred while processing {file.file.filename}.", # pyright: ignore reportPrivateUsage=none f"⚠️ An error occurred while processing {using_file_name}.", # pyright: ignore reportPrivateUsage=none
"error", "error",
) )
return create_response( return create_response(
f"{file.file.filename} is not supported.", # pyright: ignore reportPrivateUsage=none f"{using_file_name} is not supported.", # pyright: ignore reportPrivateUsage=none
"error", "error",
) )

View File

@ -19,7 +19,7 @@ vi.mock("@/lib/context/SupabaseProvider", () => ({
useSupabase: () => mockUseSupabase(), useSupabase: () => mockUseSupabase(),
})); }));
vi.mock("@/services/analytics/useEventTracking", () => ({ vi.mock("@/services/analytics/june/useEventTracking", () => ({
useEventTracking: () => ({ track: vi.fn() }), useEventTracking: () => ({ track: vi.fn() }),
})); }));

View File

@ -15,7 +15,7 @@ const queryClient = new QueryClient();
// This wrapper is used to make effect calls at a high level in app rendering. // This wrapper is used to make effect calls at a high level in app rendering.
export const App = ({ children }: PropsWithChildren): JSX.Element => { export const App = ({ children }: PropsWithChildren): JSX.Element => {
const { fetchAllBrains, fetchAndSetActiveBrain, fetchPublicPrompts } = const { fetchAllBrains, fetchDefaultBrain, fetchPublicPrompts } =
useBrainContext(); useBrainContext();
const { session } = useSupabase(); const { session } = useSupabase();
@ -23,9 +23,9 @@ export const App = ({ children }: PropsWithChildren): JSX.Element => {
useEffect(() => { useEffect(() => {
void fetchAllBrains(); void fetchAllBrains();
void fetchAndSetActiveBrain(); void fetchDefaultBrain();
void fetchPublicPrompts(); void fetchPublicPrompts();
}, [session?.user]); }, [session?.user.id]);
return ( return (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>

View File

@ -1,10 +1,8 @@
"use client"; "use client";
import { UUID } from "crypto"; import { UUID } from "crypto";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, motion } from "framer-motion";
import Link from "next/link";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Button from "@/lib/components/ui/Button";
import Spinner from "@/lib/components/ui/Spinner"; import Spinner from "@/lib/components/ui/Spinner";
import DocumentItem from "./DocumentItem"; import DocumentItem from "./DocumentItem";
@ -45,9 +43,9 @@ export const KnowledgeTab = ({ brainId }: KnowledgeTabProps): JSX.Element => {
) : ( ) : (
<div className="flex flex-col items-center justify-center mt-10 gap-1"> <div className="flex flex-col items-center justify-center mt-10 gap-1">
<p className="text-center">{t("empty", { ns: "explore" })}</p> <p className="text-center">{t("empty", { ns: "explore" })}</p>
<Link href="/upload"> <p className="text-center">
<Button>{t("uploadButton")}</Button> {t("feed_brain_instructions", { ns: "explore" })}
</Link> </p>
</div> </div>
)} )}
</motion.div> </motion.div>

View File

@ -13,7 +13,8 @@ export const ActionsBar = (): JSX.Element => {
setHasPendingRequests, setHasPendingRequests,
} = useActionBar(); } = useActionBar();
const { addContent, contents, feedBrain, removeContent } = useKnowledgeUploader({ const { addContent, contents, feedBrain, removeContent } =
useKnowledgeUploader({
setHasPendingRequests, setHasPendingRequests,
setShouldDisplayUploadCard, setShouldDisplayUploadCard,
}); });
@ -25,13 +26,17 @@ export const ActionsBar = (): JSX.Element => {
{hasPendingRequests && ( {hasPendingRequests && (
<div className="flex mt-1 flex-col md:flex-row w-full shadow-md dark:shadow-primary/25 hover:shadow-xl transition-shadow rounded-xl bg-white dark:bg-black border border-black/10 dark:border-white/25 p-2 md:p-6 pl-6"> <div className="flex mt-1 flex-col md:flex-row w-full shadow-md dark:shadow-primary/25 hover:shadow-xl transition-shadow rounded-xl bg-white dark:bg-black border border-black/10 dark:border-white/25 p-2 md:p-6 pl-6">
<div className="flex flex-1 items-center mb-2 md:mb-0"> <div className="flex flex-1 items-center mb-2 md:mb-0">
<span className="text-sm md:text-1xl">{t("filesUploading")}</span> <span className="text-sm md:text-1xl">{t("feedingBrain")}</span>
</div> </div>
<AiOutlineLoading3Quarters className="animate-spin text-2xl md:text-3xl self-center" /> <AiOutlineLoading3Quarters className="animate-spin text-2xl md:text-3xl self-center" />
</div> </div>
)} )}
<div className={shouldDisplayUploadCard ? "h-full flex flex-col flex-auto" : ""}> <div
className={
shouldDisplayUploadCard ? "h-full flex flex-col flex-auto" : ""
}
>
{shouldDisplayUploadCard && ( {shouldDisplayUploadCard && (
<div className="flex flex-1 overflow-y-scroll shadow-md dark:shadow-primary/25 hover:shadow-xl transition-shadow rounded-xl bg-white dark:bg-black border border-black/10 dark:border-white/25 p-4 md:p-6"> <div className="flex flex-1 overflow-y-scroll shadow-md dark:shadow-primary/25 hover:shadow-xl transition-shadow rounded-xl bg-white dark:bg-black border border-black/10 dark:border-white/25 p-4 md:p-6">
<KnowledgeToFeed <KnowledgeToFeed

View File

@ -40,13 +40,7 @@ export const ConfigModal = ({ chatId }: { chatId?: string }): JSX.Element => {
setOpen={setIsConfigModalOpen} setOpen={setIsConfigModalOpen}
CloseTrigger={<div />} CloseTrigger={<div />}
> >
<form <form className="mt-10 flex flex-col items-center gap-2">
onSubmit={(e) => {
void handleSubmit(e);
setIsConfigModalOpen(false);
}}
className="mt-10 flex flex-col items-center gap-2"
>
<fieldset className="w-full flex flex-col"> <fieldset className="w-full flex flex-col">
<label className="flex-1 text-sm" htmlFor="model"> <label className="flex-1 text-sm" htmlFor="model">
Model Model
@ -91,7 +85,14 @@ export const ConfigModal = ({ chatId }: { chatId?: string }): JSX.Element => {
/> />
</fieldset> </fieldset>
<Button className="mt-12 self-end" type="submit"> <Button
className="mt-12 self-end"
type="button"
onClick={() => {
handleSubmit();
setIsConfigModalOpen(false);
}}
>
Save Save
<MdCheck className="text-xl" /> <MdCheck className="text-xl" />
</Button> </Button>

View File

@ -1,6 +1,6 @@
/* eslint-disable max-lines */ /* eslint-disable max-lines */
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { FormEvent, useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useBrainApi } from "@/lib/api/brain/useBrainApi"; import { useBrainApi } from "@/lib/api/brain/useBrainApi";
@ -91,8 +91,7 @@ export const useConfigModal = (chatId?: string) => {
setValue("maxTokens", Math.min(maxTokens, defineMaxTokens(model))); setValue("maxTokens", Math.min(maxTokens, defineMaxTokens(model)));
}, [maxTokens, model, setValue]); }, [maxTokens, model, setValue]);
const handleSubmit = (e: FormEvent) => { const handleSubmit = () => {
e.preventDefault();
if (chatId === undefined) { if (chatId === undefined) {
return; return;
} }

View File

@ -167,11 +167,6 @@ export const useKnowledgeUploader = ({
} else { } else {
await fetchNotifications(currentChatId); await fetchNotifications(currentChatId);
} }
publish({
variant: "success",
text: t("knowledgeUploaded"),
});
} catch (e) { } catch (e) {
publish({ publish({
variant: "danger", variant: "danger",

View File

@ -27,9 +27,18 @@ vi.mock("next/navigation", async () => {
vi.mock("@/lib/context/ChatsProvider/hooks/useChatsContext", () => ({ vi.mock("@/lib/context/ChatsProvider/hooks/useChatsContext", () => ({
useChatsContext: () => ({ useChatsContext: () => ({
allChats: [ allChats: [
{ chat_id: 1, name: "Chat 1" }, {
{ chat_id: 2, name: "Chat 2" }, chat_id: 1,
name: "Chat 1",
creation_time: new Date().toISOString(),
},
{
chat_id: 2,
name: "Chat 2",
creation_time: new Date().toISOString(),
},
], ],
deleteChat: vi.fn(), deleteChat: vi.fn(),
setAllChats: vi.fn(), setAllChats: vi.fn(),
}), }),

View File

@ -1,28 +1,42 @@
/* eslint-disable max-lines */
/* eslint-disable complexity */ /* eslint-disable complexity */
"use client"; "use client";
import { motion, MotionConfig } from "framer-motion"; import { motion, MotionConfig } from "framer-motion";
import { MdChevronRight } from "react-icons/md"; import { MdChevronRight } from "react-icons/md";
import { useChatsContext } from "@/lib/context/ChatsProvider/hooks/useChatsContext"; import { useChatsContext } from "@/lib/context/ChatsProvider/hooks/useChatsContext";
import { cn, isToday, isWithinLast30Days, isWithinLast7Days, isYesterday } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { ChatsListItem } from "./components/ChatsListItem"; import { ChatsListItem } from "./components/ChatsListItem";
import { MiniFooter } from "./components/ChatsListItem/components/MiniFooter"; import { MiniFooter } from "./components/ChatsListItem/components/MiniFooter";
import { NewChatButton } from "./components/NewChatButton"; import { NewChatButton } from "./components/NewChatButton";
import { useChatsList } from "./hooks/useChatsList"; import { useChatsList } from "./hooks/useChatsList";
import {
isToday,
isWithinLast30Days,
isWithinLast7Days,
isYesterday,
} from "./utils";
import { useSelectedChatPage } from "../../[chatId]/hooks/useSelectedChatPage"; import { useSelectedChatPage } from "../../[chatId]/hooks/useSelectedChatPage";
export const ChatsList = (): JSX.Element => { export const ChatsList = (): JSX.Element => {
const { allChats } = useChatsContext(); const { allChats } = useChatsContext();
const { open, setOpen } = useChatsList(); const { open, setOpen } = useChatsList();
useSelectedChatPage(); useSelectedChatPage();
const todayChats = allChats.filter((chat) =>
// Filtering chats into different groups isToday(new Date(chat.creation_time))
const todayChats = allChats.filter(chat => isToday(new Date(chat.creation_time))); );
const yesterdayChats = allChats.filter(chat => isYesterday(new Date(chat.creation_time))); const yesterdayChats = allChats.filter((chat) =>
const last7DaysChats = allChats.filter(chat => isWithinLast7Days(new Date(chat.creation_time))); isYesterday(new Date(chat.creation_time))
const last30DaysChats = allChats.filter(chat => isWithinLast30Days(new Date(chat.creation_time))); );
const last7DaysChats = allChats.filter((chat) =>
isWithinLast7Days(new Date(chat.creation_time))
);
const last30DaysChats = allChats.filter((chat) =>
isWithinLast30Days(new Date(chat.creation_time))
);
return ( return (
<MotionConfig transition={{ mass: 1, damping: 10 }}> <MotionConfig transition={{ mass: 1, damping: 10 }}>
@ -56,26 +70,41 @@ export const ChatsList = (): JSX.Element => {
data-testid="chats-list-items" data-testid="chats-list-items"
className="flex-1 overflow-auto scrollbar h-full" className="flex-1 overflow-auto scrollbar h-full"
> >
{todayChats.length > 0 && <div className="bg-gray-100 text-black rounded-md px-3 py-1 mt-2">Today</div>} {todayChats.length > 0 && (
<div className="bg-gray-100 text-black rounded-md px-3 py-1 mt-2">
Today
</div>
)}
{todayChats.map((chat) => ( {todayChats.map((chat) => (
<ChatsListItem key={chat.chat_id} chat={chat} /> <ChatsListItem key={chat.chat_id} chat={chat} />
))} ))}
{yesterdayChats.length > 0 && <div className="bg-gray-100 text-black rounded-md px-3 py-1 mt-2">Yesterday</div>} {yesterdayChats.length > 0 && (
<div className="bg-gray-100 text-black rounded-md px-3 py-1 mt-2">
Yesterday
</div>
)}
{yesterdayChats.map((chat) => ( {yesterdayChats.map((chat) => (
<ChatsListItem key={chat.chat_id} chat={chat} /> <ChatsListItem key={chat.chat_id} chat={chat} />
))} ))}
{last7DaysChats.length > 0 && <div className="bg-gray-100 text-black rounded-md px-3 py-1 mt-2">Previous 7 Days</div>} {last7DaysChats.length > 0 && (
<div className="bg-gray-100 text-black rounded-md px-3 py-1 mt-2">
Previous 7 Days
</div>
)}
{last7DaysChats.map((chat) => ( {last7DaysChats.map((chat) => (
<ChatsListItem key={chat.chat_id} chat={chat} /> <ChatsListItem key={chat.chat_id} chat={chat} />
))} ))}
{last30DaysChats.length > 0 && <div className="bg-gray-100 text-black rounded-md px-3 py-1 mt-2">Previous 30 Days</div>} {last30DaysChats.length > 0 && (
<div className="bg-gray-100 text-black rounded-md px-3 py-1 mt-2">
Previous 30 Days
</div>
)}
{last30DaysChats.map((chat) => ( {last30DaysChats.map((chat) => (
<ChatsListItem key={chat.chat_id} chat={chat} /> <ChatsListItem key={chat.chat_id} chat={chat} />
))} ))}
</div> </div>
<MiniFooter /> <MiniFooter />
</div> </div>

View File

@ -0,0 +1,31 @@
export const isToday = (date: Date): boolean => {
const today = new Date();
return date.toDateString() === today.toDateString();
};
export const isYesterday = (date: Date): boolean => {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
return date.toDateString() === yesterday.toDateString();
};
export const isWithinLast7Days = (date: Date): boolean => {
const weekAgo = new Date();
weekAgo.setDate(weekAgo.getDate() - 7);
return date > weekAgo && !isToday(date) && !isYesterday(date);
};
export const isWithinLast30Days = (date: Date): boolean => {
const monthAgo = new Date();
monthAgo.setDate(monthAgo.getDate() - 30);
return (
date > monthAgo &&
!isToday(date) &&
!isYesterday(date) &&
!isWithinLast7Days(date)
);
};

View File

@ -31,7 +31,7 @@ export const useInvitation = () => {
throw new Error(t("brainUndefined", { ns: "brain" })); throw new Error(t("brainUndefined", { ns: "brain" }));
} }
const { fetchAllBrains, setActiveBrain } = useBrainContext(); const { fetchAllBrains, setCurrentBrainId } = useBrainContext();
const router = useRouter(); const router = useRouter();
useEffect(() => { useEffect(() => {
@ -73,7 +73,7 @@ export const useInvitation = () => {
variant: "success", variant: "success",
text: t("accept", { ns: "invitation" }), text: t("accept", { ns: "invitation" }),
}); });
setActiveBrain({ id: brainId, name: brainName }); setCurrentBrainId(brainId);
} catch (error) { } catch (error) {
if (axios.isAxiosError(error) && error.response?.data !== undefined) { if (axios.isAxiosError(error) && error.response?.data !== undefined) {
publish({ publish({

View File

@ -7,7 +7,6 @@ import { ToastProvider } from "@/lib/components/ui/Toast";
import { FeatureFlagsProvider } from "@/lib/context"; import { FeatureFlagsProvider } from "@/lib/context";
import { BrainProvider } from "@/lib/context/BrainProvider"; import { BrainProvider } from "@/lib/context/BrainProvider";
import { SupabaseProvider } from "@/lib/context/SupabaseProvider"; import { SupabaseProvider } from "@/lib/context/SupabaseProvider";
import { GoogleAnalytics } from "@/services/analytics/google/googleAnalytics";
import { App } from "./App"; import { App } from "./App";
import "./globals.css"; import "./globals.css";
@ -50,7 +49,6 @@ const RootLayout = async ({
</SupabaseProvider> </SupabaseProvider>
</ToastProvider> </ToastProvider>
<VercelAnalytics /> <VercelAnalytics />
<GoogleAnalytics />
</FeatureFlagsProvider> </FeatureFlagsProvider>
</body> </body>
</html> </html>

View File

@ -15,22 +15,18 @@ import { getAccessibleModels } from "@/lib/helpers/getAccessibleModels";
import { useToast } from "@/lib/hooks"; import { useToast } from "@/lib/hooks";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useAddBrainModal = () => { export const useAddBrainModal = () => {
const { t } = useTranslation(["translation", "brain", "config"]); const { t } = useTranslation(["translation", "brain", "config"]);
const [isPending, setIsPending] = useState(false); const [isPending, setIsPending] = useState(false);
const { publish } = useToast(); const { publish } = useToast();
const { createBrain, setActiveBrain } = useBrainContext(); const { createBrain, setCurrentBrainId } = useBrainContext();
const { setAsDefaultBrain } = useBrainApi(); const { setAsDefaultBrain } = useBrainApi();
const { createPrompt } = usePromptApi(); const { createPrompt } = usePromptApi();
const [isShareModalOpen, setIsShareModalOpen] = useState(false); const [isShareModalOpen, setIsShareModalOpen] = useState(false);
const { getUser } = useUserApi(); const { getUser } = useUserApi();
const { data: userData } = useQuery({ const { data: userData } = useQuery({
queryKey: [USER_DATA_KEY], queryKey: [USER_DATA_KEY],
queryFn: getUser, queryFn: getUser,
@ -46,10 +42,6 @@ export const useAddBrainModal = () => {
}, },
}; };
const { register, getValues, reset, watch, setValue } = useForm({ const { register, getValues, reset, watch, setValue } = useForm({
defaultValues, defaultValues,
}); });
@ -114,10 +106,7 @@ export const useAddBrainModal = () => {
return; return;
} }
setActiveBrain({ setCurrentBrainId(createdBrainId);
id: createdBrainId,
name,
});
if (setDefault) { if (setDefault) {
await setAsDefaultBrain(createdBrainId); await setAsDefaultBrain(createdBrainId);

View File

@ -1,15 +0,0 @@
import { Brain } from "../types";
const BRAIN_LOCAL_STORAGE_KEY = "userBrains";
export const saveBrainInLocalStorage = (brain: Brain): void => {
localStorage.setItem(BRAIN_LOCAL_STORAGE_KEY, JSON.stringify(brain));
};
export const getBrainFromLocalStorage = (): Brain | undefined => {
const persistedBrain = localStorage.getItem(BRAIN_LOCAL_STORAGE_KEY);
if (persistedBrain === null) {
return;
}
return JSON.parse(persistedBrain) as Brain;
};

View File

@ -1,6 +1,6 @@
/* eslint-disable max-lines */ /* eslint-disable max-lines */
import { UUID } from "crypto"; import { UUID } from "crypto";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useState } from "react";
import { CreateBrainInput } from "@/lib/api/brain/types"; import { CreateBrainInput } from "@/lib/api/brain/types";
import { useBrainApi } from "@/lib/api/brain/useBrainApi"; import { useBrainApi } from "@/lib/api/brain/useBrainApi";
@ -9,14 +9,8 @@ import { useToast } from "@/lib/hooks";
import { Prompt } from "@/lib/types/Prompt"; import { Prompt } from "@/lib/types/Prompt";
import { useEventTracking } from "@/services/analytics/june/useEventTracking"; import { useEventTracking } from "@/services/analytics/june/useEventTracking";
import {
getBrainFromLocalStorage,
saveBrainInLocalStorage,
} from "../helpers/brainLocalStorage";
import { MinimalBrainForUser } from "../types"; import { MinimalBrainForUser } from "../types";
// CAUTION: This hook should be use in BrainProvider only. You may be need `useBrainContext` instead.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useBrainProvider = () => { export const useBrainProvider = () => {
const { publish } = useToast(); const { publish } = useToast();
@ -36,13 +30,14 @@ export const useBrainProvider = () => {
(prompt) => prompt.id === currentPromptId (prompt) => prompt.id === currentPromptId
); );
const currentBrain = allBrains.find((brain) => brain.id === currentBrainId); const currentBrain = allBrains.find((brain) => brain.id === currentBrainId);
const createBrainHandler = async (
brain: CreateBrainInput const createBrainHandler = useCallback(
): Promise<UUID | undefined> => { async (brain: CreateBrainInput): Promise<UUID | undefined> => {
const createdBrain = await createBrain(brain); const createdBrain = await createBrain(brain);
try { try {
setAllBrains((prevBrains) => [...prevBrains, createdBrain]); setAllBrains((prevBrains) => [...prevBrains, createdBrain]);
saveBrainInLocalStorage(createdBrain); setCurrentBrainId(createdBrain.id);
void track("BRAIN_CREATED"); void track("BRAIN_CREATED");
return createdBrain.id; return createdBrain.id;
@ -52,17 +47,24 @@ export const useBrainProvider = () => {
text: "Error occurred while creating a brain", text: "Error occurred while creating a brain",
}); });
} }
}; },
[createBrain, publish, track]
);
const deleteBrainHandler = async (id: UUID) => { const deleteBrainHandler = useCallback(
async (id: UUID) => {
await deleteBrain(id); await deleteBrain(id);
setAllBrains((prevBrains) => prevBrains.filter((brain) => brain.id !== id)); setAllBrains((prevBrains) =>
prevBrains.filter((brain) => brain.id !== id)
);
void track("DELETE_BRAIN"); void track("DELETE_BRAIN");
publish({ publish({
variant: "success", variant: "success",
text: "Brain deleted", text: "Brain deleted",
}); });
}; },
[deleteBrain, publish, track]
);
const fetchAllBrains = useCallback(async () => { const fetchAllBrains = useCallback(async () => {
setIsFetchingBrains(true); setIsFetchingBrains(true);
@ -76,64 +78,41 @@ export const useBrainProvider = () => {
} }
}, [getBrains]); }, [getBrains]);
const setActiveBrain = useCallback( const fetchDefaultBrain = useCallback(async () => {
({ id, name }: { id: UUID; name: string }) => {
const newActiveBrain = { id, name };
saveBrainInLocalStorage(newActiveBrain);
setCurrentBrainId(id);
},
[]
);
const setDefaultBrain = useCallback(async () => {
const userDefaultBrain = await getDefaultBrain(); const userDefaultBrain = await getDefaultBrain();
if (userDefaultBrain !== undefined) { if (userDefaultBrain !== undefined) {
saveBrainInLocalStorage(userDefaultBrain); setDefaultBrainId(userDefaultBrain.id);
setActiveBrain(userDefaultBrain);
} else {
console.warn("No brains found");
} }
}, [getDefaultBrain, setActiveBrain]); if (currentBrainId === null && userDefaultBrain !== undefined) {
setCurrentBrainId(userDefaultBrain.id);
const fetchAndSetActiveBrain = useCallback(async () => {
const storedBrain = getBrainFromLocalStorage();
if (storedBrain?.id !== undefined) {
setActiveBrain({ ...storedBrain });
} else {
await setDefaultBrain();
} }
}, [setDefaultBrain, setActiveBrain]); }, [currentBrainId, getDefaultBrain]);
const fetchDefaultBrain = async () => { const fetchPublicPrompts = useCallback(async () => {
setDefaultBrainId((await getDefaultBrain())?.id);
};
const fetchPublicPrompts = async () => {
setPublicPrompts(await getPublicPrompts()); setPublicPrompts(await getPublicPrompts());
}; }, [getPublicPrompts]);
useEffect(() => {
void fetchDefaultBrain();
}, []);
return { return {
allBrains,
fetchAllBrains,
isFetchingBrains,
currentBrain, currentBrain,
currentBrainId, currentBrainId,
allBrains,
createBrain: createBrainHandler,
deleteBrain: deleteBrainHandler,
setActiveBrain,
setCurrentBrainId, setCurrentBrainId,
fetchAllBrains,
setDefaultBrain,
fetchAndSetActiveBrain,
isFetchingBrains,
defaultBrainId, defaultBrainId,
fetchDefaultBrain, fetchDefaultBrain,
fetchPublicPrompts, fetchPublicPrompts,
publicPrompts, publicPrompts,
currentPrompt, currentPrompt,
setCurrentPromptId, setCurrentPromptId,
currentPromptId, currentPromptId,
createBrain: createBrainHandler,
deleteBrain: deleteBrainHandler,
}; };
}; };

View File

@ -4,33 +4,3 @@ import { twMerge } from "tailwind-merge";
export const cn = (...inputs: ClassValue[]): string => { export const cn = (...inputs: ClassValue[]): string => {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
}; };
const isToday = (date: Date): boolean => {
const today = new Date();
return date.toDateString() === today.toDateString();
};
const isYesterday = (date: Date): boolean => {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
return date.toDateString() === yesterday.toDateString();
};
const isWithinLast7Days = (date: Date): boolean => {
const weekAgo = new Date();
weekAgo.setDate(weekAgo.getDate() - 7);
return date > weekAgo && !isToday(date) && !isYesterday(date);
};
const isWithinLast30Days = (date: Date): boolean => {
const monthAgo = new Date();
monthAgo.setDate(monthAgo.getDate() - 30);
return date > monthAgo && !isToday(date) && !isYesterday(date) && !isWithinLast7Days(date);
};
export { isToday, isWithinLast30Days, isWithinLast7Days, isYesterday };

View File

@ -35,6 +35,6 @@
"missing_brain": "Please select a brain to chat with", "missing_brain": "Please select a brain to chat with",
"new_prompt": "Create new prompt", "new_prompt": "Create new prompt",
"feed_brain_placeholder": "Choose which @brain you want to feed with these files", "feed_brain_placeholder": "Choose which @brain you want to feed with these files",
"filesUploading": "Your files are being processed, please wait before asking a question about them", "feedingBrain": "You newly added knowledge is being processed, you can keep chatting in the meantime !",
"add_content_card_button_tooltip": "Add content to a brain" "add_content_card_button_tooltip": "Add knowledge to a brain"
} }

View File

@ -10,5 +10,6 @@
"chunkNumber": "No. of chunks: {{quantity}}", "chunkNumber": "No. of chunks: {{quantity}}",
"notAvailable": "Not Available", "notAvailable": "Not Available",
"deleteConfirmTitle": "Confirm", "deleteConfirmTitle": "Confirm",
"deleteConfirmText": "Do you really want to delete?" "deleteConfirmText": "Do you really want to delete?",
"feed_brain_instructions":"To add knowledge to a brain, go to chat page then click on plus button on the left of the chat input"
} }

View File

@ -15,6 +15,5 @@
"ohNo": "Oh no!", "ohNo": "Oh no!",
"selectBrainFirst": "You need to select a brain first. 🧠💡🥲", "selectBrainFirst": "You need to select a brain first. 🧠💡🥲",
"missingNecessaryRole": "You don't have the necessary role to upload content to the selected brain. 🧠💡🥲", "missingNecessaryRole": "You don't have the necessary role to upload content to the selected brain. 🧠💡🥲",
"invalidFileType": "Invalid file type", "invalidFileType": "Invalid file type"
"knowledgeUploaded": "Files successfully uploaded to the brain !"
} }

View File

@ -36,6 +36,6 @@
"missing_brain": "No hay cerebro seleccionado", "missing_brain": "No hay cerebro seleccionado",
"new_prompt": "Crear nueva instrucción", "new_prompt": "Crear nueva instrucción",
"feed_brain_placeholder" : "Elige cuál @cerebro quieres alimentar con estos archivos", "feed_brain_placeholder" : "Elige cuál @cerebro quieres alimentar con estos archivos",
"filesUploading": "Subiendo archivos...", "feedingBrain": "Su conocimiento recién agregado se está procesando, ¡puede seguir chateando mientras tanto!",
"add_content_card_button_tooltip": "Agregar contenido a un cerebro" "add_content_card_button_tooltip": "Agregar conocimiento a un cerebro"
} }

View File

@ -10,5 +10,6 @@
"sessionNotFound": "Sesión no encontrada", "sessionNotFound": "Sesión no encontrada",
"subtitle": "Ver o borrar datos guardados usados por tu cerebro", "subtitle": "Ver o borrar datos guardados usados por tu cerebro",
"title": "Explora datos subidos", "title": "Explora datos subidos",
"view": "Ver" "view": "Ver",
"feed_brain_instructions": "Para agregar conocimiento a un cerebro, ve a la página de chat y haz clic en el botón de más a la izquierda del campo de chat."
} }

View File

@ -15,6 +15,5 @@
"success": "Archivo subido correctamente", "success": "Archivo subido correctamente",
"title": "Subir conocimiento", "title": "Subir conocimiento",
"webSite": "Ingrese la URL del sitio web", "webSite": "Ingrese la URL del sitio web",
"invalidFileType": "Tipo de archivo no permitido", "invalidFileType": "Tipo de archivo no permitido"
"knowledgeUploaded": "Conocimiento subido"
} }

View File

@ -36,6 +36,6 @@
"missing_brain": "Veuillez selectionner un cerveau pour discuter", "missing_brain": "Veuillez selectionner un cerveau pour discuter",
"new_prompt": "Créer un nouveau prompt", "new_prompt": "Créer un nouveau prompt",
"feed_brain_placeholder" : "Choisissez le @cerveau que vous souhaitez nourrir avec ces fichiers", "feed_brain_placeholder" : "Choisissez le @cerveau que vous souhaitez nourrir avec ces fichiers",
"filesUploading": "Téléchargement des fichiers...", "feedingBrain": "Vos nouvelles connaissances sont en cours de traitement. Vous pouvez continuer à discuter en attendant !",
"add_content_card_button_tooltip": "Ajouter du contenu à un cerveau" "add_content_card_button_tooltip": "Ajouter des connaissances à un cerveau"
} }

View File

@ -10,5 +10,6 @@
"chunkNumber": "Nombre de fragments : {{quantity}}", "chunkNumber": "Nombre de fragments : {{quantity}}",
"notAvailable": "Non disponible", "notAvailable": "Non disponible",
"deleteConfirmTitle": "Confirmer", "deleteConfirmTitle": "Confirmer",
"deleteConfirmText": "Voulez-vous vraiment supprimer ?" "deleteConfirmText": "Voulez-vous vraiment supprimer ?",
"feed_brain_instructions": "Pour ajouter des connaissances à un cerveau, allez sur la page de chat, puis cliquez sur le bouton plus à gauche de la zone de chat."
} }

View File

@ -15,6 +15,5 @@
"ohNo": "Oh non !", "ohNo": "Oh non !",
"selectBrainFirst": "Vous devez d'abord sélectionner un cerveau. 🧠💡🥲", "selectBrainFirst": "Vous devez d'abord sélectionner un cerveau. 🧠💡🥲",
"missingNecessaryRole": "Vous n'avez pas le rôle nécessaire pour télécharger du contenu dans le cerveau sélectionné. 🧠💡🥲", "missingNecessaryRole": "Vous n'avez pas le rôle nécessaire pour télécharger du contenu dans le cerveau sélectionné. 🧠💡🥲",
"invalidFileType": "Type de fichier invalide", "invalidFileType": "Type de fichier invalide"
"knowledgeUploaded": "Connaissance téléchargée"
} }

View File

@ -36,6 +36,6 @@
"missing_brain": "Cérebro não encontrado", "missing_brain": "Cérebro não encontrado",
"new_prompt": "Criar novo prompt", "new_prompt": "Criar novo prompt",
"feed_brain_placeholder" : "Escolha qual @cérebro você deseja alimentar com esses arquivos", "feed_brain_placeholder" : "Escolha qual @cérebro você deseja alimentar com esses arquivos",
"filesUploading":"Arquivos sendo enviados", "feedingBrain": "Seu conhecimento recém-adicionado está sendo processado, você pode continuar conversando enquanto isso!",
"add_content_card_button_tooltip":"Adicionar conteúdo a um cérebro" "add_content_card_button_tooltip": "Adicionar conhecimento a um cérebro"
} }

View File

@ -10,5 +10,8 @@
"chunkNumber": "Número de partes: {{quantity}}", "chunkNumber": "Número de partes: {{quantity}}",
"notAvailable": "Indisponível", "notAvailable": "Indisponível",
"deleteConfirmTitle": "Confirmar", "deleteConfirmTitle": "Confirmar",
"deleteConfirmText": "Você realmente deseja excluir?" "deleteConfirmText": "Você realmente deseja excluir?",
"feed_brain_instructions": "Para adicionar conhecimento a um cérebro, vá para a página de chat e clique no botão de adição à esquerda da entrada de chat."
} }

View File

@ -15,6 +15,5 @@
"ohNo": "Oh, não!", "ohNo": "Oh, não!",
"selectBrainFirst": "Você precisa selecionar um cérebro primeiro. 🧠💡🥲", "selectBrainFirst": "Você precisa selecionar um cérebro primeiro. 🧠💡🥲",
"missingNecessaryRole": "Você não possui a função necessária para enviar conteúdo para o cérebro selecionado. 🧠💡🥲", "missingNecessaryRole": "Você não possui a função necessária para enviar conteúdo para o cérebro selecionado. 🧠💡🥲",
"invalidFileType": "Tipo de arquivo inválido", "invalidFileType": "Tipo de arquivo inválido"
"knowledgeUploaded": "Conhecimento enviado"
} }

View File

@ -36,7 +36,6 @@
"missing_brain": "Мозг не найден", "missing_brain": "Мозг не найден",
"new_prompt": "Создать новый запрос", "new_prompt": "Создать новый запрос",
"feed_brain_placeholder" : "Выберите, какой @мозг вы хотите питать этими файлами", "feed_brain_placeholder" : "Выберите, какой @мозг вы хотите питать этими файлами",
"filesUploading": "Загрузка файлов...", "feedingBrain": "Ваш недавно добавленный знаний обрабатывается, вы можете продолжить общение в это время!",
"add_content_card_button_tooltip": "Добавить контент в мозг" "add_content_card_button_tooltip": "Добавить знаний в мозг"
} }

View File

@ -10,5 +10,6 @@
"chunkNumber": "Количество частей: {{quantity}}", "chunkNumber": "Количество частей: {{quantity}}",
"notAvailable": "Не доступно", "notAvailable": "Не доступно",
"deleteConfirmTitle": "Подтвердите", "deleteConfirmTitle": "Подтвердите",
"deleteConfirmText": "Вы действительно хотите удалить?" "deleteConfirmText": "Вы действительно хотите удалить?",
"feed_brain_instructions": "Чтобы добавить знания в мозг, перейдите на страницу чата, затем нажмите на кнопку плюс слева от поля ввода чата."
} }

View File

@ -15,6 +15,5 @@
"ohNo": "О нет!", "ohNo": "О нет!",
"selectBrainFirst": "Сначала вам нужно выбрать мозг. 🧠💡🥲", "selectBrainFirst": "Сначала вам нужно выбрать мозг. 🧠💡🥲",
"missingNecessaryRole": "У вас нет необходимой роли для загрузки контента в выбранный мозг. 🧠💡🥲", "missingNecessaryRole": "У вас нет необходимой роли для загрузки контента в выбранный мозг. 🧠💡🥲",
"invalidFileType": "Неверный тип файла", "invalidFileType": "Неверный тип файла"
"knowledgeUploaded": "Знания загружены"
} }

View File

@ -36,6 +36,6 @@
"missing_brain": "请选择一个大脑进行聊天", "missing_brain": "请选择一个大脑进行聊天",
"new_prompt": "新提示", "new_prompt": "新提示",
"feed_brain_placeholder" : "选择要用这些文件喂养的 @大脑", "feed_brain_placeholder" : "选择要用这些文件喂养的 @大脑",
"filesUploading": "文件上传中...", "feedingBrain": "您新添加的知识正在处理中,同时您可以继续聊天!",
"add_content_card_button_tooltip":"向大脑添加内容" "add_content_card_button_tooltip": "向大脑添加知识"
} }

View File

@ -10,5 +10,6 @@
"chunkNumber": "No. of chunks: {{quantity}}", "chunkNumber": "No. of chunks: {{quantity}}",
"notAvailable": "不可用", "notAvailable": "不可用",
"deleteConfirmTitle": "提交", "deleteConfirmTitle": "提交",
"deleteConfirmText": "你真的要删除吗?" "deleteConfirmText": "你真的要删除吗?",
"feed_brain_instructions": "要向大脑添加知识,请转到聊天页面,然后单击聊天输入框左侧的加号按钮。"
} }

View File

@ -15,6 +15,5 @@
"ohNo": "不好了!", "ohNo": "不好了!",
"selectBrainFirst": "你需要先选择一个大脑。 🧠💡🥲", "selectBrainFirst": "你需要先选择一个大脑。 🧠💡🥲",
"missingNecessaryRole": "您没有所选大脑的上传权限。 🧠💡🥲", "missingNecessaryRole": "您没有所选大脑的上传权限。 🧠💡🥲",
"invalidFileType": "文件类型不受支持", "invalidFileType": "文件类型不受支持"
"knowledgeUploaded": "知识已上传"
} }

View File

@ -1,29 +0,0 @@
import Script from "next/script";
export const GoogleAnalytics = (): JSX.Element => {
const ga_id = process.env.NEXT_PUBLIC_GA_ID;
if (ga_id === undefined) {
return <></>;
}
return (
<>
<Script
async
src={`https://www.googletagmanager.com/gtag/js?id=${ga_id}`}
/>
<Script
id="ga-config"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${ga_id}');
`,
}}
/>
</>
);
};

View File

@ -21,9 +21,8 @@ export const useGAnalyticsEventTracker = ({
action: string; action: string;
label?: string; label?: string;
}) => { }) => {
ReactGA.event({ ReactGA.event(action, {
category, category,
action,
label, label,
}); });
}; };