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")
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:

View File

@ -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,

View File

@ -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",
)

View File

@ -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() }),
}));

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.
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}>

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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;
}

View File

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

View File

@ -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(),
}),

View File

@ -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>

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" }));
}
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({

View File

@ -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>

View File

@ -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);

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 */
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,
};
};

View File

@ -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 };

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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."
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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."
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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."
}

View File

@ -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"
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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;
label?: string;
}) => {
ReactGA.event({
ReactGA.event(action, {
category,
action,
label,
});
};