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")
|
@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:
|
||||||
|
@ -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,
|
||||||
|
@ -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",
|
||||||
)
|
)
|
||||||
|
@ -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() }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -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}>
|
||||||
|
@ -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>
|
||||||
|
@ -12,11 +12,12 @@ export const ActionsBar = (): JSX.Element => {
|
|||||||
hasPendingRequests,
|
hasPendingRequests,
|
||||||
setHasPendingRequests,
|
setHasPendingRequests,
|
||||||
} = useActionBar();
|
} = useActionBar();
|
||||||
|
|
||||||
const { addContent, contents, feedBrain, removeContent } = useKnowledgeUploader({
|
const { addContent, contents, feedBrain, removeContent } =
|
||||||
setHasPendingRequests,
|
useKnowledgeUploader({
|
||||||
setShouldDisplayUploadCard,
|
setHasPendingRequests,
|
||||||
});
|
setShouldDisplayUploadCard,
|
||||||
|
});
|
||||||
|
|
||||||
const { t } = useTranslation(["chat"]);
|
const { t } = useTranslation(["chat"]);
|
||||||
|
|
||||||
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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(),
|
||||||
}),
|
}),
|
||||||
|
@ -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>
|
||||||
|
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" }));
|
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({
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
@ -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 */
|
/* 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,33 +30,41 @@ 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
|
|
||||||
): Promise<UUID | undefined> => {
|
|
||||||
const createdBrain = await createBrain(brain);
|
|
||||||
try {
|
|
||||||
setAllBrains((prevBrains) => [...prevBrains, createdBrain]);
|
|
||||||
saveBrainInLocalStorage(createdBrain);
|
|
||||||
void track("BRAIN_CREATED");
|
|
||||||
|
|
||||||
return createdBrain.id;
|
const createBrainHandler = useCallback(
|
||||||
} catch {
|
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({
|
publish({
|
||||||
variant: "danger",
|
variant: "success",
|
||||||
text: "Error occurred while creating a brain",
|
text: "Brain deleted",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
};
|
[deleteBrain, publish, track]
|
||||||
|
);
|
||||||
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",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -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 };
|
|
||||||
|
@ -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"
|
||||||
}
|
}
|
@ -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"
|
||||||
}
|
}
|
@ -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 !"
|
|
||||||
}
|
}
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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."
|
||||||
}
|
}
|
@ -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"
|
|
||||||
}
|
}
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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."
|
||||||
}
|
}
|
@ -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"
|
|
||||||
}
|
}
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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."
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -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"
|
|
||||||
}
|
}
|
@ -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": "Добавить знаний в мозг"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -10,5 +10,6 @@
|
|||||||
"chunkNumber": "Количество частей: {{quantity}}",
|
"chunkNumber": "Количество частей: {{quantity}}",
|
||||||
"notAvailable": "Не доступно",
|
"notAvailable": "Не доступно",
|
||||||
"deleteConfirmTitle": "Подтвердите",
|
"deleteConfirmTitle": "Подтвердите",
|
||||||
"deleteConfirmText": "Вы действительно хотите удалить?"
|
"deleteConfirmText": "Вы действительно хотите удалить?",
|
||||||
|
"feed_brain_instructions": "Чтобы добавить знания в мозг, перейдите на страницу чата, затем нажмите на кнопку плюс слева от поля ввода чата."
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,5 @@
|
|||||||
"ohNo": "О нет!",
|
"ohNo": "О нет!",
|
||||||
"selectBrainFirst": "Сначала вам нужно выбрать мозг. 🧠💡🥲",
|
"selectBrainFirst": "Сначала вам нужно выбрать мозг. 🧠💡🥲",
|
||||||
"missingNecessaryRole": "У вас нет необходимой роли для загрузки контента в выбранный мозг. 🧠💡🥲",
|
"missingNecessaryRole": "У вас нет необходимой роли для загрузки контента в выбранный мозг. 🧠💡🥲",
|
||||||
"invalidFileType": "Неверный тип файла",
|
"invalidFileType": "Неверный тип файла"
|
||||||
"knowledgeUploaded": "Знания загружены"
|
|
||||||
}
|
}
|
||||||
|
@ -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": "向大脑添加知识"
|
||||||
}
|
}
|
||||||
|
@ -10,5 +10,6 @@
|
|||||||
"chunkNumber": "No. of chunks: {{quantity}}",
|
"chunkNumber": "No. of chunks: {{quantity}}",
|
||||||
"notAvailable": "不可用",
|
"notAvailable": "不可用",
|
||||||
"deleteConfirmTitle": "提交",
|
"deleteConfirmTitle": "提交",
|
||||||
"deleteConfirmText": "你真的要删除吗?"
|
"deleteConfirmText": "你真的要删除吗?",
|
||||||
|
"feed_brain_instructions": "要向大脑添加知识,请转到聊天页面,然后单击聊天输入框左侧的加号按钮。"
|
||||||
}
|
}
|
@ -15,6 +15,5 @@
|
|||||||
"ohNo": "不好了!",
|
"ohNo": "不好了!",
|
||||||
"selectBrainFirst": "你需要先选择一个大脑。 🧠💡🥲",
|
"selectBrainFirst": "你需要先选择一个大脑。 🧠💡🥲",
|
||||||
"missingNecessaryRole": "您没有所选大脑的上传权限。 🧠💡🥲",
|
"missingNecessaryRole": "您没有所选大脑的上传权限。 🧠💡🥲",
|
||||||
"invalidFileType": "文件类型不受支持",
|
"invalidFileType": "文件类型不受支持"
|
||||||
"knowledgeUploaded": "知识已上传"
|
|
||||||
}
|
}
|
@ -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;
|
action: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
}) => {
|
}) => {
|
||||||
ReactGA.event({
|
ReactGA.event(action, {
|
||||||
category,
|
category,
|
||||||
action,
|
|
||||||
label,
|
label,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user