mirror of
https://github.com/StanGirard/quivr.git
synced 2024-11-23 12:26:03 +03:00
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:
parent
8914c7c357
commit
1ec736b357
@ -49,6 +49,7 @@ else:
|
||||
@celery.task(name="process_file_and_notify")
|
||||
def process_file_and_notify(
|
||||
file_name: str,
|
||||
file_original_name: str,
|
||||
enable_summarization,
|
||||
brain_id,
|
||||
openai_api_key,
|
||||
@ -77,6 +78,7 @@ def process_file_and_notify(
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
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,
|
||||
brain_id=brain_id,
|
||||
openai_api_key=openai_api_key,
|
||||
original_file_name=crawl_website_url,
|
||||
)
|
||||
)
|
||||
else:
|
||||
|
@ -4,23 +4,21 @@ from uuid import UUID
|
||||
from auth import AuthBearer, get_current_user
|
||||
from celery_worker import process_file_and_notify
|
||||
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 (
|
||||
CreateNotificationProperties,
|
||||
NotificationUpdatableProperties,
|
||||
)
|
||||
from models.notifications import NotificationsStatusEnum
|
||||
from repository.brain import get_brain_details
|
||||
from repository.files.upload_file import upload_file_storage
|
||||
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 utils.file import convert_bytes, get_file_size
|
||||
|
||||
from routes.authorizations.brain_authorization import (
|
||||
RoleEnum,
|
||||
validate_brain_authorization,
|
||||
)
|
||||
from utils.file import convert_bytes, get_file_size
|
||||
from utils.processors import filter_file
|
||||
|
||||
upload_router = APIRouter()
|
||||
|
||||
@ -86,6 +84,7 @@ async def upload_file(
|
||||
upload_file_storage(file_content, filename_with_brain_id)
|
||||
process_file_and_notify.delay(
|
||||
file_name=filename_with_brain_id,
|
||||
file_original_name=uploadFile.filename,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
openai_api_key=openai_api_key,
|
||||
|
@ -1,6 +1,7 @@
|
||||
from models.brains import Brain
|
||||
from models.files import File
|
||||
from parsers.audio import process_audio
|
||||
from parsers.code_python import process_python
|
||||
from parsers.csv import process_csv
|
||||
from parsers.docx import process_docx
|
||||
from parsers.epub import process_epub
|
||||
@ -12,7 +13,7 @@ from parsers.pdf import process_pdf
|
||||
from parsers.powerpoint import process_powerpoint
|
||||
from parsers.txt import process_txt
|
||||
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 = {
|
||||
".txt": process_txt,
|
||||
@ -48,27 +49,32 @@ async def filter_file(
|
||||
enable_summarization: bool,
|
||||
brain_id,
|
||||
openai_api_key,
|
||||
original_file_name=None,
|
||||
):
|
||||
await file.compute_file_sha1()
|
||||
|
||||
print("file sha1", file.file_sha1)
|
||||
file_exists = file.file_already_exists()
|
||||
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:
|
||||
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",
|
||||
)
|
||||
elif file.file_is_empty():
|
||||
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
|
||||
)
|
||||
elif file_exists:
|
||||
file.link_file_to_brain(brain=Brain(id=brain_id))
|
||||
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",
|
||||
)
|
||||
|
||||
@ -81,18 +87,18 @@ async def filter_file(
|
||||
user_openai_api_key=openai_api_key,
|
||||
)
|
||||
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",
|
||||
)
|
||||
except Exception as e:
|
||||
# Add more specific exceptions as needed.
|
||||
print(f"Error processing file: {e}")
|
||||
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",
|
||||
)
|
||||
|
||||
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",
|
||||
)
|
||||
|
@ -19,7 +19,7 @@ vi.mock("@/lib/context/SupabaseProvider", () => ({
|
||||
useSupabase: () => mockUseSupabase(),
|
||||
}));
|
||||
|
||||
vi.mock("@/services/analytics/useEventTracking", () => ({
|
||||
vi.mock("@/services/analytics/june/useEventTracking", () => ({
|
||||
useEventTracking: () => ({ track: vi.fn() }),
|
||||
}));
|
||||
|
||||
|
@ -15,7 +15,7 @@ const queryClient = new QueryClient();
|
||||
|
||||
// This wrapper is used to make effect calls at a high level in app rendering.
|
||||
export const App = ({ children }: PropsWithChildren): JSX.Element => {
|
||||
const { fetchAllBrains, fetchAndSetActiveBrain, fetchPublicPrompts } =
|
||||
const { fetchAllBrains, fetchDefaultBrain, fetchPublicPrompts } =
|
||||
useBrainContext();
|
||||
const { session } = useSupabase();
|
||||
|
||||
@ -23,9 +23,9 @@ export const App = ({ children }: PropsWithChildren): JSX.Element => {
|
||||
|
||||
useEffect(() => {
|
||||
void fetchAllBrains();
|
||||
void fetchAndSetActiveBrain();
|
||||
void fetchDefaultBrain();
|
||||
void fetchPublicPrompts();
|
||||
}, [session?.user]);
|
||||
}, [session?.user.id]);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
|
@ -1,10 +1,8 @@
|
||||
"use client";
|
||||
import { UUID } from "crypto";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import Spinner from "@/lib/components/ui/Spinner";
|
||||
|
||||
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">
|
||||
<p className="text-center">{t("empty", { ns: "explore" })}</p>
|
||||
<Link href="/upload">
|
||||
<Button>{t("uploadButton")}</Button>
|
||||
</Link>
|
||||
<p className="text-center">
|
||||
{t("feed_brain_instructions", { ns: "explore" })}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
|
@ -12,11 +12,12 @@ export const ActionsBar = (): JSX.Element => {
|
||||
hasPendingRequests,
|
||||
setHasPendingRequests,
|
||||
} = useActionBar();
|
||||
|
||||
const { addContent, contents, feedBrain, removeContent } = useKnowledgeUploader({
|
||||
setHasPendingRequests,
|
||||
setShouldDisplayUploadCard,
|
||||
});
|
||||
|
||||
const { addContent, contents, feedBrain, removeContent } =
|
||||
useKnowledgeUploader({
|
||||
setHasPendingRequests,
|
||||
setShouldDisplayUploadCard,
|
||||
});
|
||||
|
||||
const { t } = useTranslation(["chat"]);
|
||||
|
||||
@ -25,13 +26,17 @@ export const ActionsBar = (): JSX.Element => {
|
||||
{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 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>
|
||||
<AiOutlineLoading3Quarters className="animate-spin text-2xl md:text-3xl self-center" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={shouldDisplayUploadCard ? "h-full flex flex-col flex-auto" : ""}>
|
||||
<div
|
||||
className={
|
||||
shouldDisplayUploadCard ? "h-full flex flex-col flex-auto" : ""
|
||||
}
|
||||
>
|
||||
{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">
|
||||
<KnowledgeToFeed
|
||||
|
@ -40,13 +40,7 @@ export const ConfigModal = ({ chatId }: { chatId?: string }): JSX.Element => {
|
||||
setOpen={setIsConfigModalOpen}
|
||||
CloseTrigger={<div />}
|
||||
>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
void handleSubmit(e);
|
||||
setIsConfigModalOpen(false);
|
||||
}}
|
||||
className="mt-10 flex flex-col items-center gap-2"
|
||||
>
|
||||
<form className="mt-10 flex flex-col items-center gap-2">
|
||||
<fieldset className="w-full flex flex-col">
|
||||
<label className="flex-1 text-sm" htmlFor="model">
|
||||
Model
|
||||
@ -91,7 +85,14 @@ export const ConfigModal = ({ chatId }: { chatId?: string }): JSX.Element => {
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<Button className="mt-12 self-end" type="submit">
|
||||
<Button
|
||||
className="mt-12 self-end"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
handleSubmit();
|
||||
setIsConfigModalOpen(false);
|
||||
}}
|
||||
>
|
||||
Save
|
||||
<MdCheck className="text-xl" />
|
||||
</Button>
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* eslint-disable max-lines */
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { FormEvent, useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { useBrainApi } from "@/lib/api/brain/useBrainApi";
|
||||
@ -91,8 +91,7 @@ export const useConfigModal = (chatId?: string) => {
|
||||
setValue("maxTokens", Math.min(maxTokens, defineMaxTokens(model)));
|
||||
}, [maxTokens, model, setValue]);
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
const handleSubmit = () => {
|
||||
if (chatId === undefined) {
|
||||
return;
|
||||
}
|
||||
|
@ -167,11 +167,6 @@ export const useKnowledgeUploader = ({
|
||||
} else {
|
||||
await fetchNotifications(currentChatId);
|
||||
}
|
||||
|
||||
publish({
|
||||
variant: "success",
|
||||
text: t("knowledgeUploaded"),
|
||||
});
|
||||
} catch (e) {
|
||||
publish({
|
||||
variant: "danger",
|
||||
|
@ -27,9 +27,18 @@ vi.mock("next/navigation", async () => {
|
||||
vi.mock("@/lib/context/ChatsProvider/hooks/useChatsContext", () => ({
|
||||
useChatsContext: () => ({
|
||||
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(),
|
||||
setAllChats: vi.fn(),
|
||||
}),
|
||||
|
@ -1,28 +1,42 @@
|
||||
/* eslint-disable max-lines */
|
||||
/* eslint-disable complexity */
|
||||
"use client";
|
||||
import { motion, MotionConfig } from "framer-motion";
|
||||
import { MdChevronRight } from "react-icons/md";
|
||||
|
||||
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 { MiniFooter } from "./components/ChatsListItem/components/MiniFooter";
|
||||
import { NewChatButton } from "./components/NewChatButton";
|
||||
import { useChatsList } from "./hooks/useChatsList";
|
||||
import {
|
||||
isToday,
|
||||
isWithinLast30Days,
|
||||
isWithinLast7Days,
|
||||
isYesterday,
|
||||
} from "./utils";
|
||||
import { useSelectedChatPage } from "../../[chatId]/hooks/useSelectedChatPage";
|
||||
|
||||
export const ChatsList = (): JSX.Element => {
|
||||
const { allChats } = useChatsContext();
|
||||
|
||||
const { open, setOpen } = useChatsList();
|
||||
useSelectedChatPage();
|
||||
|
||||
|
||||
// Filtering chats into different groups
|
||||
const todayChats = allChats.filter(chat => isToday(new Date(chat.creation_time)));
|
||||
const yesterdayChats = allChats.filter(chat => isYesterday(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)));
|
||||
const todayChats = allChats.filter((chat) =>
|
||||
isToday(new Date(chat.creation_time))
|
||||
);
|
||||
const yesterdayChats = allChats.filter((chat) =>
|
||||
isYesterday(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 (
|
||||
<MotionConfig transition={{ mass: 1, damping: 10 }}>
|
||||
@ -56,26 +70,41 @@ export const ChatsList = (): JSX.Element => {
|
||||
data-testid="chats-list-items"
|
||||
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) => (
|
||||
<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) => (
|
||||
<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) => (
|
||||
<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) => (
|
||||
<ChatsListItem key={chat.chat_id} chat={chat} />
|
||||
))}
|
||||
|
||||
</div>
|
||||
<MiniFooter />
|
||||
</div>
|
||||
|
31
frontend/app/chat/components/ChatsList/utils.ts
Normal file
31
frontend/app/chat/components/ChatsList/utils.ts
Normal 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)
|
||||
);
|
||||
};
|
@ -31,7 +31,7 @@ export const useInvitation = () => {
|
||||
throw new Error(t("brainUndefined", { ns: "brain" }));
|
||||
}
|
||||
|
||||
const { fetchAllBrains, setActiveBrain } = useBrainContext();
|
||||
const { fetchAllBrains, setCurrentBrainId } = useBrainContext();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
@ -73,7 +73,7 @@ export const useInvitation = () => {
|
||||
variant: "success",
|
||||
text: t("accept", { ns: "invitation" }),
|
||||
});
|
||||
setActiveBrain({ id: brainId, name: brainName });
|
||||
setCurrentBrainId(brainId);
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error) && error.response?.data !== undefined) {
|
||||
publish({
|
||||
|
@ -7,7 +7,6 @@ import { ToastProvider } from "@/lib/components/ui/Toast";
|
||||
import { FeatureFlagsProvider } from "@/lib/context";
|
||||
import { BrainProvider } from "@/lib/context/BrainProvider";
|
||||
import { SupabaseProvider } from "@/lib/context/SupabaseProvider";
|
||||
import { GoogleAnalytics } from "@/services/analytics/google/googleAnalytics";
|
||||
|
||||
import { App } from "./App";
|
||||
import "./globals.css";
|
||||
@ -50,7 +49,6 @@ const RootLayout = async ({
|
||||
</SupabaseProvider>
|
||||
</ToastProvider>
|
||||
<VercelAnalytics />
|
||||
<GoogleAnalytics />
|
||||
</FeatureFlagsProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -15,22 +15,18 @@ import { getAccessibleModels } from "@/lib/helpers/getAccessibleModels";
|
||||
import { useToast } from "@/lib/hooks";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useAddBrainModal = () => {
|
||||
const { t } = useTranslation(["translation", "brain", "config"]);
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const { publish } = useToast();
|
||||
const { createBrain, setActiveBrain } = useBrainContext();
|
||||
const { createBrain, setCurrentBrainId } = useBrainContext();
|
||||
const { setAsDefaultBrain } = useBrainApi();
|
||||
const { createPrompt } = usePromptApi();
|
||||
const [isShareModalOpen, setIsShareModalOpen] = useState(false);
|
||||
|
||||
const { getUser } = useUserApi();
|
||||
|
||||
|
||||
const { data: userData } = useQuery({
|
||||
queryKey: [USER_DATA_KEY],
|
||||
queryFn: getUser,
|
||||
@ -46,10 +42,6 @@ export const useAddBrainModal = () => {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const { register, getValues, reset, watch, setValue } = useForm({
|
||||
defaultValues,
|
||||
});
|
||||
@ -114,10 +106,7 @@ export const useAddBrainModal = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveBrain({
|
||||
id: createdBrainId,
|
||||
name,
|
||||
});
|
||||
setCurrentBrainId(createdBrainId);
|
||||
|
||||
if (setDefault) {
|
||||
await setAsDefaultBrain(createdBrainId);
|
||||
|
@ -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;
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
/* eslint-disable max-lines */
|
||||
import { UUID } from "crypto";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
import { CreateBrainInput } from "@/lib/api/brain/types";
|
||||
import { useBrainApi } from "@/lib/api/brain/useBrainApi";
|
||||
@ -9,14 +9,8 @@ import { useToast } from "@/lib/hooks";
|
||||
import { Prompt } from "@/lib/types/Prompt";
|
||||
import { useEventTracking } from "@/services/analytics/june/useEventTracking";
|
||||
|
||||
import {
|
||||
getBrainFromLocalStorage,
|
||||
saveBrainInLocalStorage,
|
||||
} from "../helpers/brainLocalStorage";
|
||||
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
|
||||
export const useBrainProvider = () => {
|
||||
const { publish } = useToast();
|
||||
@ -36,33 +30,41 @@ export const useBrainProvider = () => {
|
||||
(prompt) => prompt.id === currentPromptId
|
||||
);
|
||||
const currentBrain = allBrains.find((brain) => brain.id === currentBrainId);
|
||||
const createBrainHandler = async (
|
||||
brain: CreateBrainInput
|
||||
): Promise<UUID | undefined> => {
|
||||
const createdBrain = await createBrain(brain);
|
||||
try {
|
||||
setAllBrains((prevBrains) => [...prevBrains, createdBrain]);
|
||||
saveBrainInLocalStorage(createdBrain);
|
||||
void track("BRAIN_CREATED");
|
||||
|
||||
return createdBrain.id;
|
||||
} catch {
|
||||
const createBrainHandler = useCallback(
|
||||
async (brain: CreateBrainInput): Promise<UUID | undefined> => {
|
||||
const createdBrain = await createBrain(brain);
|
||||
try {
|
||||
setAllBrains((prevBrains) => [...prevBrains, createdBrain]);
|
||||
setCurrentBrainId(createdBrain.id);
|
||||
|
||||
void track("BRAIN_CREATED");
|
||||
|
||||
return createdBrain.id;
|
||||
} catch {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: "Error occurred while creating a brain",
|
||||
});
|
||||
}
|
||||
},
|
||||
[createBrain, publish, track]
|
||||
);
|
||||
|
||||
const deleteBrainHandler = useCallback(
|
||||
async (id: UUID) => {
|
||||
await deleteBrain(id);
|
||||
setAllBrains((prevBrains) =>
|
||||
prevBrains.filter((brain) => brain.id !== id)
|
||||
);
|
||||
void track("DELETE_BRAIN");
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: "Error occurred while creating a brain",
|
||||
variant: "success",
|
||||
text: "Brain deleted",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const deleteBrainHandler = async (id: UUID) => {
|
||||
await deleteBrain(id);
|
||||
setAllBrains((prevBrains) => prevBrains.filter((brain) => brain.id !== id));
|
||||
void track("DELETE_BRAIN");
|
||||
publish({
|
||||
variant: "success",
|
||||
text: "Brain deleted",
|
||||
});
|
||||
};
|
||||
},
|
||||
[deleteBrain, publish, track]
|
||||
);
|
||||
|
||||
const fetchAllBrains = useCallback(async () => {
|
||||
setIsFetchingBrains(true);
|
||||
@ -76,64 +78,41 @@ export const useBrainProvider = () => {
|
||||
}
|
||||
}, [getBrains]);
|
||||
|
||||
const setActiveBrain = useCallback(
|
||||
({ id, name }: { id: UUID; name: string }) => {
|
||||
const newActiveBrain = { id, name };
|
||||
saveBrainInLocalStorage(newActiveBrain);
|
||||
setCurrentBrainId(id);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const setDefaultBrain = useCallback(async () => {
|
||||
const fetchDefaultBrain = useCallback(async () => {
|
||||
const userDefaultBrain = await getDefaultBrain();
|
||||
if (userDefaultBrain !== undefined) {
|
||||
saveBrainInLocalStorage(userDefaultBrain);
|
||||
setActiveBrain(userDefaultBrain);
|
||||
} else {
|
||||
console.warn("No brains found");
|
||||
setDefaultBrainId(userDefaultBrain.id);
|
||||
}
|
||||
}, [getDefaultBrain, setActiveBrain]);
|
||||
|
||||
const fetchAndSetActiveBrain = useCallback(async () => {
|
||||
const storedBrain = getBrainFromLocalStorage();
|
||||
if (storedBrain?.id !== undefined) {
|
||||
setActiveBrain({ ...storedBrain });
|
||||
} else {
|
||||
await setDefaultBrain();
|
||||
if (currentBrainId === null && userDefaultBrain !== undefined) {
|
||||
setCurrentBrainId(userDefaultBrain.id);
|
||||
}
|
||||
}, [setDefaultBrain, setActiveBrain]);
|
||||
}, [currentBrainId, getDefaultBrain]);
|
||||
|
||||
const fetchDefaultBrain = async () => {
|
||||
setDefaultBrainId((await getDefaultBrain())?.id);
|
||||
};
|
||||
|
||||
const fetchPublicPrompts = async () => {
|
||||
const fetchPublicPrompts = useCallback(async () => {
|
||||
setPublicPrompts(await getPublicPrompts());
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
void fetchDefaultBrain();
|
||||
}, []);
|
||||
}, [getPublicPrompts]);
|
||||
|
||||
return {
|
||||
allBrains,
|
||||
fetchAllBrains,
|
||||
isFetchingBrains,
|
||||
|
||||
currentBrain,
|
||||
currentBrainId,
|
||||
allBrains,
|
||||
createBrain: createBrainHandler,
|
||||
deleteBrain: deleteBrainHandler,
|
||||
setActiveBrain,
|
||||
setCurrentBrainId,
|
||||
fetchAllBrains,
|
||||
setDefaultBrain,
|
||||
fetchAndSetActiveBrain,
|
||||
isFetchingBrains,
|
||||
|
||||
defaultBrainId,
|
||||
fetchDefaultBrain,
|
||||
|
||||
fetchPublicPrompts,
|
||||
publicPrompts,
|
||||
currentPrompt,
|
||||
|
||||
setCurrentPromptId,
|
||||
currentPromptId,
|
||||
|
||||
createBrain: createBrainHandler,
|
||||
|
||||
deleteBrain: deleteBrainHandler,
|
||||
};
|
||||
};
|
||||
|
@ -4,33 +4,3 @@ import { twMerge } from "tailwind-merge";
|
||||
export const cn = (...inputs: ClassValue[]): string => {
|
||||
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 };
|
||||
|
@ -35,6 +35,6 @@
|
||||
"missing_brain": "Please select a brain to chat with",
|
||||
"new_prompt": "Create new prompt",
|
||||
"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",
|
||||
"add_content_card_button_tooltip": "Add content to a brain"
|
||||
"feedingBrain": "You newly added knowledge is being processed, you can keep chatting in the meantime !",
|
||||
"add_content_card_button_tooltip": "Add knowledge to a brain"
|
||||
}
|
@ -10,5 +10,6 @@
|
||||
"chunkNumber": "No. of chunks: {{quantity}}",
|
||||
"notAvailable": "Not Available",
|
||||
"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"
|
||||
}
|
@ -15,6 +15,5 @@
|
||||
"ohNo": "Oh no!",
|
||||
"selectBrainFirst": "You need to select a brain first. 🧠💡🥲",
|
||||
"missingNecessaryRole": "You don't have the necessary role to upload content to the selected brain. 🧠💡🥲",
|
||||
"invalidFileType": "Invalid file type",
|
||||
"knowledgeUploaded": "Files successfully uploaded to the brain !"
|
||||
"invalidFileType": "Invalid file type"
|
||||
}
|
@ -36,6 +36,6 @@
|
||||
"missing_brain": "No hay cerebro seleccionado",
|
||||
"new_prompt": "Crear nueva instrucción",
|
||||
"feed_brain_placeholder" : "Elige cuál @cerebro quieres alimentar con estos archivos",
|
||||
"filesUploading": "Subiendo archivos...",
|
||||
"add_content_card_button_tooltip": "Agregar contenido a un cerebro"
|
||||
"feedingBrain": "Su conocimiento recién agregado se está procesando, ¡puede seguir chateando mientras tanto!",
|
||||
"add_content_card_button_tooltip": "Agregar conocimiento a un cerebro"
|
||||
}
|
||||
|
@ -10,5 +10,6 @@
|
||||
"sessionNotFound": "Sesión no encontrada",
|
||||
"subtitle": "Ver o borrar datos guardados usados por tu cerebro",
|
||||
"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."
|
||||
}
|
@ -15,6 +15,5 @@
|
||||
"success": "Archivo subido correctamente",
|
||||
"title": "Subir conocimiento",
|
||||
"webSite": "Ingrese la URL del sitio web",
|
||||
"invalidFileType": "Tipo de archivo no permitido",
|
||||
"knowledgeUploaded": "Conocimiento subido"
|
||||
"invalidFileType": "Tipo de archivo no permitido"
|
||||
}
|
@ -36,6 +36,6 @@
|
||||
"missing_brain": "Veuillez selectionner un cerveau pour discuter",
|
||||
"new_prompt": "Créer un nouveau prompt",
|
||||
"feed_brain_placeholder" : "Choisissez le @cerveau que vous souhaitez nourrir avec ces fichiers",
|
||||
"filesUploading": "Téléchargement des fichiers...",
|
||||
"add_content_card_button_tooltip": "Ajouter du contenu à un cerveau"
|
||||
"feedingBrain": "Vos nouvelles connaissances sont en cours de traitement. Vous pouvez continuer à discuter en attendant !",
|
||||
"add_content_card_button_tooltip": "Ajouter des connaissances à un cerveau"
|
||||
}
|
||||
|
@ -10,5 +10,6 @@
|
||||
"chunkNumber": "Nombre de fragments : {{quantity}}",
|
||||
"notAvailable": "Non disponible",
|
||||
"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."
|
||||
}
|
@ -15,6 +15,5 @@
|
||||
"ohNo": "Oh non !",
|
||||
"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é. 🧠💡🥲",
|
||||
"invalidFileType": "Type de fichier invalide",
|
||||
"knowledgeUploaded": "Connaissance téléchargée"
|
||||
"invalidFileType": "Type de fichier invalide"
|
||||
}
|
@ -36,6 +36,6 @@
|
||||
"missing_brain": "Cérebro não encontrado",
|
||||
"new_prompt": "Criar novo prompt",
|
||||
"feed_brain_placeholder" : "Escolha qual @cérebro você deseja alimentar com esses arquivos",
|
||||
"filesUploading":"Arquivos sendo enviados",
|
||||
"add_content_card_button_tooltip":"Adicionar conteúdo a um cérebro"
|
||||
"feedingBrain": "Seu conhecimento recém-adicionado está sendo processado, você pode continuar conversando enquanto isso!",
|
||||
"add_content_card_button_tooltip": "Adicionar conhecimento a um cérebro"
|
||||
}
|
||||
|
@ -10,5 +10,8 @@
|
||||
"chunkNumber": "Número de partes: {{quantity}}",
|
||||
"notAvailable": "Indisponível",
|
||||
"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."
|
||||
|
||||
|
||||
}
|
@ -15,6 +15,5 @@
|
||||
"ohNo": "Oh, não!",
|
||||
"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. 🧠💡🥲",
|
||||
"invalidFileType": "Tipo de arquivo inválido",
|
||||
"knowledgeUploaded": "Conhecimento enviado"
|
||||
"invalidFileType": "Tipo de arquivo inválido"
|
||||
}
|
@ -36,7 +36,6 @@
|
||||
"missing_brain": "Мозг не найден",
|
||||
"new_prompt": "Создать новый запрос",
|
||||
"feed_brain_placeholder" : "Выберите, какой @мозг вы хотите питать этими файлами",
|
||||
"filesUploading": "Загрузка файлов...",
|
||||
"add_content_card_button_tooltip": "Добавить контент в мозг"
|
||||
|
||||
"feedingBrain": "Ваш недавно добавленный знаний обрабатывается, вы можете продолжить общение в это время!",
|
||||
"add_content_card_button_tooltip": "Добавить знаний в мозг"
|
||||
}
|
||||
|
@ -10,5 +10,6 @@
|
||||
"chunkNumber": "Количество частей: {{quantity}}",
|
||||
"notAvailable": "Не доступно",
|
||||
"deleteConfirmTitle": "Подтвердите",
|
||||
"deleteConfirmText": "Вы действительно хотите удалить?"
|
||||
"deleteConfirmText": "Вы действительно хотите удалить?",
|
||||
"feed_brain_instructions": "Чтобы добавить знания в мозг, перейдите на страницу чата, затем нажмите на кнопку плюс слева от поля ввода чата."
|
||||
}
|
||||
|
@ -15,6 +15,5 @@
|
||||
"ohNo": "О нет!",
|
||||
"selectBrainFirst": "Сначала вам нужно выбрать мозг. 🧠💡🥲",
|
||||
"missingNecessaryRole": "У вас нет необходимой роли для загрузки контента в выбранный мозг. 🧠💡🥲",
|
||||
"invalidFileType": "Неверный тип файла",
|
||||
"knowledgeUploaded": "Знания загружены"
|
||||
"invalidFileType": "Неверный тип файла"
|
||||
}
|
||||
|
@ -36,6 +36,6 @@
|
||||
"missing_brain": "请选择一个大脑进行聊天",
|
||||
"new_prompt": "新提示",
|
||||
"feed_brain_placeholder" : "选择要用这些文件喂养的 @大脑",
|
||||
"filesUploading": "文件上传中...",
|
||||
"add_content_card_button_tooltip":"向大脑添加内容"
|
||||
"feedingBrain": "您新添加的知识正在处理中,同时您可以继续聊天!",
|
||||
"add_content_card_button_tooltip": "向大脑添加知识"
|
||||
}
|
||||
|
@ -10,5 +10,6 @@
|
||||
"chunkNumber": "No. of chunks: {{quantity}}",
|
||||
"notAvailable": "不可用",
|
||||
"deleteConfirmTitle": "提交",
|
||||
"deleteConfirmText": "你真的要删除吗?"
|
||||
"deleteConfirmText": "你真的要删除吗?",
|
||||
"feed_brain_instructions": "要向大脑添加知识,请转到聊天页面,然后单击聊天输入框左侧的加号按钮。"
|
||||
}
|
@ -15,6 +15,5 @@
|
||||
"ohNo": "不好了!",
|
||||
"selectBrainFirst": "你需要先选择一个大脑。 🧠💡🥲",
|
||||
"missingNecessaryRole": "您没有所选大脑的上传权限。 🧠💡🥲",
|
||||
"invalidFileType": "文件类型不受支持",
|
||||
"knowledgeUploaded": "知识已上传"
|
||||
"invalidFileType": "文件类型不受支持"
|
||||
}
|
@ -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}');
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -21,9 +21,8 @@ export const useGAnalyticsEventTracker = ({
|
||||
action: string;
|
||||
label?: string;
|
||||
}) => {
|
||||
ReactGA.event({
|
||||
ReactGA.event(action, {
|
||||
category,
|
||||
action,
|
||||
label,
|
||||
});
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user