feat: improve app ux (#1281)

* style: make FeedItemIcon the same size

* feat: update feed component brain selector label position

* style: change purple to 600

* feat: improve already dropped file message ux

* feat: autoclose feedinput on chatId change

* style: change chat colors

* feat: prevent linebreak in knowledge to upload row

* feat(textFIeld): avoid textfield content going under icon

* feat(knowledgeToUpload): add tooltip on urls and files name

* feat(feedBrain): auto scroll on messages when feed modal get opened

* style: update colors

* refactor: rename uploadCard to FeedCard
This commit is contained in:
Mamadou DICKO 2023-09-29 10:24:31 +02:00 committed by GitHub
parent 72ef62aad0
commit 36fd146fed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 175 additions and 187 deletions

View File

@ -88,7 +88,7 @@ export const SettingsTab = ({ brainId }: SettingsTabProps): JSX.Element => {
<div className="mt-4"> <div className="mt-4">
<div className="flex flex-1 items-center flex-col"> <div className="flex flex-1 items-center flex-col">
{isPublicBrain && !isOwnedByCurrentUser && ( {isPublicBrain && !isOwnedByCurrentUser && (
<Chip className="mb-3 bg-purple-600 text-white w-full"> <Chip className="mb-3 bg-primary text-white w-full">
{t("brain:public_brain_label")} {t("brain:public_brain_label")}
</Chip> </Chip>
)} )}

View File

@ -71,7 +71,7 @@ export const BrainsList = (): JSX.Element => {
> >
<Button <Button
type="button" type="button"
className="bg-purple-600 text-white py-2 mb-2 flex flex-row flex-1" className="bg-primary text-white py-2 mb-2 flex flex-row flex-1"
> >
{t("brain_management_button_label")} {t("brain_management_button_label")}
</Button> </Button>
@ -83,7 +83,7 @@ export const BrainsList = (): JSX.Element => {
> >
<Button <Button
type="button" type="button"
className="bg-purple-600 text-white py-2 mb-2 flex flex-row flex-1" className="bg-primary text-white py-2 mb-2 flex flex-row flex-1"
> >
{t("brain_library_button_label")} {t("brain_library_button_label")}
</Button> </Button>

View File

@ -36,7 +36,7 @@ export const PublicBrainItem = ({
}} }}
disabled={isUserSubscribedToBrain || subscriptionRequestPending} disabled={isUserSubscribedToBrain || subscriptionRequestPending}
isLoading={subscriptionRequestPending} isLoading={subscriptionRequestPending}
className="bg-purple-600 text-white p-0 px-3 rounded-xl border-0 w-content mt-3" className="bg-primary text-white p-0 px-3 rounded-xl border-0 w-content mt-3"
> >
{isUserSubscribedToBrain {isUserSubscribedToBrain
? t("public_brain_already_subscribed_button_label") ? t("public_brain_already_subscribed_button_label")

View File

@ -2,21 +2,16 @@ import { AnimatePresence, motion } from "framer-motion";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { AiOutlineLoading3Quarters } from "react-icons/ai"; import { AiOutlineLoading3Quarters } from "react-icons/ai";
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
import { ChatInput, KnowledgeToFeed } from "./components"; import { ChatInput, KnowledgeToFeed } from "./components";
import { useActionBar } from "./hooks/useActionBar"; import { useActionBar } from "./hooks/useActionBar";
type ActionBarProps = { export const ActionsBar = (): JSX.Element => {
setShouldDisplayUploadCard: (shouldDisplay: boolean) => void;
shouldDisplayUploadCard: boolean;
};
export const ActionsBar = ({
setShouldDisplayUploadCard,
shouldDisplayUploadCard,
}: ActionBarProps): JSX.Element => {
const { hasPendingRequests, setHasPendingRequests } = useActionBar(); const { hasPendingRequests, setHasPendingRequests } = useActionBar();
const { t } = useTranslation(["chat"]); const { t } = useTranslation(["chat"]);
const { shouldDisplayFeedCard } = useKnowledgeToFeedContext();
return ( return (
<> <>
@ -30,7 +25,7 @@ export const ActionsBar = ({
)} )}
<div> <div>
{shouldDisplayUploadCard && ( {shouldDisplayFeedCard && (
<AnimatePresence> <AnimatePresence>
<motion.div <motion.div
key="slide" key="slide"
@ -40,19 +35,15 @@ export const ActionsBar = ({
> >
<div className="flex flex-1 overflow-y-auto 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 mt-5"> <div className="flex flex-1 overflow-y-auto 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 mt-5">
<KnowledgeToFeed <KnowledgeToFeed
closeFeedInput={() => setShouldDisplayUploadCard(false)}
dispatchHasPendingRequests={() => setHasPendingRequests(true)} dispatchHasPendingRequests={() => setHasPendingRequests(true)}
/> />
</div> </div>
</motion.div> </motion.div>
</AnimatePresence> </AnimatePresence>
)} )}
{!shouldDisplayUploadCard && ( {!shouldDisplayFeedCard && (
<div className="flex mt-1 flex-col 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 md:mb-4 lg:mb-[-20px] p-2"> <div className="flex mt-1 flex-col 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 md:mb-4 lg:mb-[-20px] p-2">
<ChatInput <ChatInput shouldDisplayFeedCard={shouldDisplayFeedCard} />
shouldDisplayUploadCard={shouldDisplayUploadCard}
setShouldDisplayUploadCard={setShouldDisplayUploadCard}
/>
</div> </div>
)} )}
</div> </div>

View File

@ -3,24 +3,25 @@ import { useTranslation } from "react-i18next";
import { PiPaperclipFill } from "react-icons/pi"; import { PiPaperclipFill } from "react-icons/pi";
import Button from "@/lib/components/ui/Button"; import Button from "@/lib/components/ui/Button";
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
import { ChatBar } from "./components/ChatBar/ChatBar"; import { ChatBar } from "./components/ChatBar/ChatBar";
import { ConfigModal } from "./components/ConfigModal"; import { ConfigModal } from "./components/ConfigModal";
import { useChatInput } from "./hooks/useChatInput"; import { useChatInput } from "./hooks/useChatInput";
type ChatInputProps = { type ChatInputProps = {
shouldDisplayUploadCard: boolean; shouldDisplayFeedCard: boolean;
setShouldDisplayUploadCard: (shouldDisplayUploadCard: boolean) => void;
}; };
export const ChatInput = ({ export const ChatInput = ({
shouldDisplayUploadCard, shouldDisplayFeedCard,
setShouldDisplayUploadCard,
}: ChatInputProps): JSX.Element => { }: ChatInputProps): JSX.Element => {
const { setMessage, submitQuestion, generatingAnswer, message } = const { setMessage, submitQuestion, generatingAnswer, message } =
useChatInput(); useChatInput();
const { t } = useTranslation(["chat"]); const { t } = useTranslation(["chat"]);
const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext();
return ( return (
<form <form
data-testid="chat-input-form" data-testid="chat-input-form"
@ -30,13 +31,13 @@ export const ChatInput = ({
}} }}
className="sticky bottom-0 bg-white dark:bg-black w-full flex items-center gap-2 z-20 p-2" className="sticky bottom-0 bg-white dark:bg-black w-full flex items-center gap-2 z-20 p-2"
> >
{!shouldDisplayUploadCard && ( {!shouldDisplayFeedCard && (
<Button <Button
className="p-0" className="p-0"
variant={"tertiary"} variant={"tertiary"}
data-testid="upload-button" data-testid="upload-button"
type="button" type="button"
onClick={() => setShouldDisplayUploadCard(true)} onClick={() => setShouldDisplayFeedCard(true)}
tooltip={t("add_content_card_button_tooltip")} tooltip={t("add_content_card_button_tooltip")}
> >
<PiPaperclipFill className="text-3xl" /> <PiPaperclipFill className="text-3xl" />

View File

@ -8,22 +8,23 @@ import Button from "@/lib/components/ui/Button";
import { Select } from "@/lib/components/ui/Select"; import { Select } from "@/lib/components/ui/Select";
import { requiredRolesForUpload } from "@/lib/config/upload"; import { requiredRolesForUpload } from "@/lib/config/upload";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
import { useFeedBrainInChat } from "./hooks/useFeedBrainInChat"; import { useFeedBrainInChat } from "./hooks/useFeedBrainInChat";
import { formatMinimalBrainsToSelectComponentInput } from "./utils/formatMinimalBrainsToSelectComponentInput"; import { formatMinimalBrainsToSelectComponentInput } from "./utils/formatMinimalBrainsToSelectComponentInput";
type KnowledgeToFeedProps = { type KnowledgeToFeedProps = {
closeFeedInput: () => void; dispatchHasPendingRequests: () => void;
dispatchHasPendingRequests?: () => void;
}; };
export const KnowledgeToFeed = ({ export const KnowledgeToFeed = ({
closeFeedInput,
dispatchHasPendingRequests, dispatchHasPendingRequests,
}: KnowledgeToFeedProps): JSX.Element => { }: KnowledgeToFeedProps): JSX.Element => {
const { allBrains, currentBrainId, setCurrentBrainId } = useBrainContext(); const { allBrains, currentBrainId, setCurrentBrainId } = useBrainContext();
const { t } = useTranslation(["upload"]); const { t } = useTranslation(["upload"]);
const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext();
const brainsWithUploadRights = useMemo( const brainsWithUploadRights = useMemo(
() => () =>
allBrains.filter((brain) => requiredRolesForUpload.includes(brain.role)), allBrains.filter((brain) => requiredRolesForUpload.includes(brain.role)),
@ -32,14 +33,16 @@ export const KnowledgeToFeed = ({
const { feedBrain } = useFeedBrainInChat({ const { feedBrain } = useFeedBrainInChat({
dispatchHasPendingRequests, dispatchHasPendingRequests,
closeFeedInput,
}); });
return ( return (
<div className="flex-col w-full relative"> <div className="flex-col w-full relative">
<div className="flex flex-1 justify-between"> <div className="flex flex-1 justify-between">
<AddBrainModal /> <AddBrainModal />
<Button variant={"tertiary"} onClick={closeFeedInput}> <Button
variant={"tertiary"}
onClick={() => setShouldDisplayFeedCard(false)}
>
<span> <span>
<MdClose className="text-3xl" /> <MdClose className="text-3xl" />
</span> </span>
@ -47,10 +50,10 @@ export const KnowledgeToFeed = ({
</div> </div>
<div className="flex justify-center"> <div className="flex justify-center">
<Select <Select
label={t("selected_brain_select_label")}
options={formatMinimalBrainsToSelectComponentInput( options={formatMinimalBrainsToSelectComponentInput(
brainsWithUploadRights brainsWithUploadRights
)} )}
emptyLabel={t("selected_brain_select_label")}
value={currentBrainId ?? undefined} value={currentBrainId ?? undefined}
onChange={(newSelectedBrainId) => onChange={(newSelectedBrainId) =>
setCurrentBrainId(newSelectedBrainId) setCurrentBrainId(newSelectedBrainId)

View File

@ -16,14 +16,13 @@ import { FeedItemCrawlType, FeedItemUploadType } from "../../../types";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useFeedBrainInChat = ({ export const useFeedBrainInChat = ({
dispatchHasPendingRequests, dispatchHasPendingRequests,
closeFeedInput,
}: { }: {
dispatchHasPendingRequests?: () => void; dispatchHasPendingRequests: () => void;
closeFeedInput?: () => void;
}) => { }) => {
const { publish } = useToast(); const { publish } = useToast();
const { t } = useTranslation(["upload"]); const { t } = useTranslation(["upload"]);
const router = useRouter(); const router = useRouter();
const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext();
const { currentBrainId } = useBrainContext(); const { currentBrainId } = useBrainContext();
const { setKnowledgeToFeed, knowledgeToFeed } = useKnowledgeToFeedContext(); const { setKnowledgeToFeed, knowledgeToFeed } = useKnowledgeToFeedContext();
@ -62,8 +61,8 @@ export const useFeedBrainInChat = ({
return; return;
} }
try { try {
dispatchHasPendingRequests?.(); dispatchHasPendingRequests();
closeFeedInput?.(); setShouldDisplayFeedCard(false);
setHasPendingRequests(true); setHasPendingRequests(true);
const currentChatId = chatId ?? (await createChat("New Chat")).chat_id; const currentChatId = chatId ?? (await createChat("New Chat")).chat_id;
const uploadPromises = files.map((file) => const uploadPromises = files.map((file) =>

View File

@ -25,16 +25,16 @@ export const MessageRow = React.forwardRef(
const handleCopy = () => { const handleCopy = () => {
navigator.clipboard.writeText(text).then( navigator.clipboard.writeText(text).then(
() => setIsCopied(true), () => setIsCopied(true),
(err) => console.error('Failed to copy!', err) (err) => console.error("Failed to copy!", err)
); );
setTimeout(() => setIsCopied(false), 2000); // Reset after 2 seconds setTimeout(() => setIsCopied(false), 2000); // Reset after 2 seconds
}; };
const containerClasses = cn( const containerClasses = cn(
"py-3 px-5 w-fit", "py-3 px-5 w-fit",
isUserSpeaker isUserSpeaker
? "bg-gray-100 bg-opacity-60 items-start" ? "bg-msg-gray bg-opacity-60 items-start"
: "bg-purple-100 bg-opacity-60 items-end", : "bg-msg-purple bg-opacity-60 items-end",
"dark:bg-gray-800 rounded-3xl flex flex-col overflow-hidden scroll-pb-32" "dark:bg-gray-800 rounded-3xl flex flex-col overflow-hidden scroll-pb-32"
); );

View File

@ -2,6 +2,7 @@ import _debounce from "lodash/debounce";
import { useCallback, useEffect, useRef } from "react"; import { useCallback, useEffect, useRef } from "react";
import { useChat } from "@/app/chat/[chatId]/hooks/useChat"; import { useChat } from "@/app/chat/[chatId]/hooks/useChat";
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
//TODO: link this to chat input to get the right height //TODO: link this to chat input to get the right height
const chatInputHeightEstimation = 100; const chatInputHeightEstimation = 100;
@ -10,6 +11,7 @@ const chatInputHeightEstimation = 100;
export const useChatDialogue = () => { export const useChatDialogue = () => {
const chatListRef = useRef<HTMLDivElement | null>(null); const chatListRef = useRef<HTMLDivElement | null>(null);
const { messages } = useChat(); const { messages } = useChat();
const { shouldDisplayFeedCard } = useKnowledgeToFeedContext();
const scrollToBottom = useCallback( const scrollToBottom = useCallback(
_debounce(() => { _debounce(() => {
@ -43,7 +45,7 @@ export const useChatDialogue = () => {
useEffect(() => { useEffect(() => {
scrollToBottom(); scrollToBottom();
}, [messages, scrollToBottom]); }, [messages, scrollToBottom, shouldDisplayFeedCard]);
return { return {
chatListRef, chatListRef,

View File

@ -22,6 +22,7 @@ export const ChatDialogue = ({
flexDirection: "column", flexDirection: "column",
flex: 1, flex: 1,
overflowY: "auto", overflowY: "auto",
marginBottom: 10,
}} }}
ref={chatListRef} ref={chatListRef}
> >

View File

@ -10,20 +10,19 @@ export const ChatHeader = (): JSX.Element => {
return ( return (
<h1 className="hidden lg:block text-3xl font-bold text-center"> <h1 className="hidden lg:block text-3xl font-bold text-center">
{t("chat_title_intro")}{" "} {t("chat_title_intro")}{" "}
<span className="text-purple-500">{t("brains")}</span> <span className="text-primary">{t("brains")}</span>
</h1> </h1>
); );
} }
return ( return (
<h1 className="hidden lg:block text-3xl font-bold text-center"> <h1 className="hidden lg:block text-3xl font-bold text-center">
{t("chat_title_intro")}{" "} {t("chat_title_intro")}{" "}
<span className="text-primary">{t("brains")}</span>
<span className="text-purple-500">{t("brains")}</span>
{" !! "} {" !! "}
<br /> <br />
{t("empty_brain_title_prefix")}{" "} {t("empty_brain_title_prefix")}{" "}
<span className="text-purple-500">{t("brain")}</span>{" "} <span className="text-primary">{t("brain")}</span>{" "}
{t("empty_brain_title_suffix")} {t("empty_brain_title_suffix")}
</h1> </h1>
); );

View File

@ -1,20 +0,0 @@
import { useEffect, useState } from "react";
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useSelectedChatPage = () => {
const [shouldDisplayUploadCard, setShouldDisplayUploadCard] = useState(false);
const { knowledgeToFeed } = useKnowledgeToFeedContext();
useEffect(() => {
if (knowledgeToFeed.length > 0 && !shouldDisplayUploadCard) {
setShouldDisplayUploadCard(true);
}
}, [knowledgeToFeed, setShouldDisplayUploadCard]);
return {
shouldDisplayUploadCard,
setShouldDisplayUploadCard,
};
};

View File

@ -1,16 +1,15 @@
"use client"; "use client";
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
import { useCustomDropzone } from "@/lib/hooks/useDropzone"; import { useCustomDropzone } from "@/lib/hooks/useDropzone";
import { ActionsBar } from "./components/ActionsBar"; import { ActionsBar } from "./components/ActionsBar";
import { ChatDialogueArea } from "./components/ChatDialogueArea/ChatDialogue"; import { ChatDialogueArea } from "./components/ChatDialogueArea/ChatDialogue";
import { ChatHeader } from "./components/ChatHeader"; import { ChatHeader } from "./components/ChatHeader";
import { useSelectedChatPage } from "./hooks/useSelectedChatPage";
const SelectedChatPage = (): JSX.Element => { const SelectedChatPage = (): JSX.Element => {
const { setShouldDisplayUploadCard, shouldDisplayUploadCard } =
useSelectedChatPage();
const { getRootProps } = useCustomDropzone(); const { getRootProps } = useCustomDropzone();
const { shouldDisplayFeedCard } = useKnowledgeToFeedContext();
return ( return (
<main <main
@ -22,16 +21,13 @@ const SelectedChatPage = (): JSX.Element => {
<ChatHeader /> <ChatHeader />
<div <div
className={`flex-1 flex flex-col mt-4 md:mt-8 w-full shadow-md dark:shadow-primary/25 hover:shadow-xl transition-shadow rounded-xl overflow-hidden dark:bg-black border border-black/10 dark:border-white/25 p-2 md:p-12 pt-4 md:pt-10 ${ className={`flex-1 flex flex-col mt-4 md:mt-8 w-full shadow-md dark:shadow-primary/25 hover:shadow-xl transition-shadow rounded-xl overflow-hidden dark:bg-black border border-black/10 dark:border-white/25 p-2 md:p-12 pt-4 md:pt-10 ${
shouldDisplayUploadCard ? "bg-gray-100" : "bg-white" shouldDisplayFeedCard ? "bg-chat-bg-gray" : "bg-white"
}`} }`}
> >
<div className="flex flex-1 flex-col overflow-y-auto"> <div className="flex flex-1 flex-col overflow-y-auto">
<ChatDialogueArea /> <ChatDialogueArea />
</div> </div>
<ActionsBar <ActionsBar />
setShouldDisplayUploadCard={setShouldDisplayUploadCard}
shouldDisplayUploadCard={shouldDisplayUploadCard}
/>
</div> </div>
</section> </section>
</main> </main>

View File

@ -11,7 +11,9 @@ import {
ChatContextMock, ChatContextMock,
ChatProviderMock, ChatProviderMock,
} from "@/lib/context/ChatProvider/mocks/ChatProviderMock"; } from "@/lib/context/ChatProvider/mocks/ChatProviderMock";
import { KnowledgeToFeedProvider } from "@/lib/context/KnowledgeToFeedProvider";
import { SupabaseContextMock } from "@/lib/context/SupabaseProvider/mocks/SupabaseProviderMock"; import { SupabaseContextMock } from "@/lib/context/SupabaseProvider/mocks/SupabaseProviderMock";
vi.mock("@/lib/context/SupabaseProvider/supabase-provider", () => ({ vi.mock("@/lib/context/SupabaseProvider/supabase-provider", () => ({
SupabaseContext: SupabaseContextMock, SupabaseContext: SupabaseContextMock,
})); }));
@ -89,11 +91,13 @@ describe("ChatsList", () => {
it("should render correctly", () => { it("should render correctly", () => {
const { getByTestId } = render( const { getByTestId } = render(
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<ChatProviderMock> <KnowledgeToFeedProvider>
<BrainProviderMock> <ChatProviderMock>
<ChatsList /> <BrainProviderMock>
</BrainProviderMock> <ChatsList />
</ChatProviderMock> </BrainProviderMock>
</ChatProviderMock>
</KnowledgeToFeedProvider>
</QueryClientProvider> </QueryClientProvider>
); );
const chatsList = getByTestId("chats-list"); const chatsList = getByTestId("chats-list");
@ -109,11 +113,13 @@ describe("ChatsList", () => {
it("renders the chats list with correct number of items", () => { it("renders the chats list with correct number of items", () => {
render( render(
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<ChatProviderMock> <KnowledgeToFeedProvider>
<BrainProviderMock> <ChatProviderMock>
<ChatsList /> <BrainProviderMock>
</BrainProviderMock> <ChatsList />
</ChatProviderMock> </BrainProviderMock>
</ChatProviderMock>
</KnowledgeToFeedProvider>
</QueryClientProvider> </QueryClientProvider>
); );
const chatItems = screen.getAllByTestId("chats-list-item"); const chatItems = screen.getAllByTestId("chats-list-item");
@ -129,11 +135,13 @@ describe("ChatsList", () => {
await act(() => await act(() =>
render( render(
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<ChatProviderMock> <KnowledgeToFeedProvider>
<BrainProviderMock> <ChatProviderMock>
<ChatsList /> <BrainProviderMock>
</BrainProviderMock> <ChatsList />
</ChatProviderMock> </BrainProviderMock>
</ChatProviderMock>
</KnowledgeToFeedProvider>
</QueryClientProvider> </QueryClientProvider>
) )
); );
@ -155,11 +163,13 @@ describe("ChatsList", () => {
await act(() => await act(() =>
render( render(
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<ChatProviderMock> <KnowledgeToFeedProvider>
<BrainProviderMock> <ChatProviderMock>
<ChatsList /> <BrainProviderMock>
</BrainProviderMock> <ChatsList />
</ChatProviderMock> </BrainProviderMock>
</ChatProviderMock>
</KnowledgeToFeedProvider>
</QueryClientProvider> </QueryClientProvider>
) )
); );

View File

@ -5,6 +5,7 @@ import { useEffect } from "react";
import { useChatApi } from "@/lib/api/chat/useChatApi"; import { useChatApi } from "@/lib/api/chat/useChatApi";
import { useNotificationApi } from "@/lib/api/notification/useNotificationApi"; import { useNotificationApi } from "@/lib/api/notification/useNotificationApi";
import { useChatContext } from "@/lib/context"; import { useChatContext } from "@/lib/context";
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
import { getChatNotificationsQueryKey } from "../../../[chatId]/utils/getChatNotificationsQueryKey"; import { getChatNotificationsQueryKey } from "../../../[chatId]/utils/getChatNotificationsQueryKey";
import { getMessagesFromChatItems } from "../../../[chatId]/utils/getMessagesFromChatItems"; import { getMessagesFromChatItems } from "../../../[chatId]/utils/getMessagesFromChatItems";
@ -15,6 +16,7 @@ export const useChatNotificationsSync = () => {
const { setMessages, setNotifications, notifications } = useChatContext(); const { setMessages, setNotifications, notifications } = useChatContext();
const { getChatItems } = useChatApi(); const { getChatItems } = useChatApi();
const { getChatNotifications } = useNotificationApi(); const { getChatNotifications } = useNotificationApi();
const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext();
const params = useParams(); const params = useParams();
const chatId = params?.chatId as string | undefined; const chatId = params?.chatId as string | undefined;
@ -54,6 +56,7 @@ export const useChatNotificationsSync = () => {
}, [fetchedNotifications]); }, [fetchedNotifications]);
useEffect(() => { useEffect(() => {
setShouldDisplayFeedCard(false);
const fetchHistory = async () => { const fetchHistory = async () => {
if (chatId === undefined) { if (chatId === undefined) {
setMessages([]); setMessages([]);

View File

@ -31,7 +31,7 @@ export const KnowledgeToFeedInput = ({
<div className="flex justify-center mt-5"> <div className="flex justify-center mt-5">
<Button <Button
disabled={knowledgeToFeed.length === 0} disabled={knowledgeToFeed.length === 0}
className="rounded-xl bg-purple-600 border-white" className="rounded-xl bg-primary border-white"
onClick={() => void feedBrain()} onClick={() => void feedBrain()}
> >
{t("feed_form_submit_button", { ns: "upload" })} {t("feed_form_submit_button", { ns: "upload" })}

View File

@ -3,7 +3,7 @@ import { Fragment } from "react";
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
import { CrawlFeedItem } from "./components/CrawlFeedItem"; import { CrawlFeedItem } from "./components/CrawlFeedItem";
import { FileFeedItem } from "./components/FileFeedItem/FileFeedItem"; import { FileFeedItem } from "./components/FileFeedItem";
export const FeedItems = (): JSX.Element => { export const FeedItems = (): JSX.Element => {
const { knowledgeToFeed, removeKnowledgeToFeed } = const { knowledgeToFeed, removeKnowledgeToFeed } =

View File

@ -14,14 +14,20 @@ export const CrawlFeedItem = ({
}: CrawlFeedItemProps): JSX.Element => { }: CrawlFeedItemProps): JSX.Element => {
return ( return (
<StyledFeedItemDiv> <StyledFeedItemDiv>
<div className="flex flex-1 items-center"> <div className="flex flex-1 overflow-hidden items-center gap-1">
<MdLink className="mr-2 text-2xl" /> <div>
<FeedTitleDisplayer title={url} /> <MdLink className="mr-2 text-2xl" />
</div>
<div className="flex flex-1">
<FeedTitleDisplayer title={url} isUrl />
</div>
</div>
<div>
<IoMdCloseCircle
className="cursor-pointer text-gray-400 text-lg"
onClick={onRemove}
/>
</div> </div>
<IoMdCloseCircle
className="cursor-pointer text-gray-400 text-lg"
onClick={onRemove}
/>
</StyledFeedItemDiv> </StyledFeedItemDiv>
); );
}; };

View File

@ -1,40 +1,24 @@
import { useState } from "react"; import Tooltip from "@/lib/components/ui/Tooltip";
import { enhanceUrlDisplay } from "./utils/enhanceUrlDisplay"; import { enhanceUrlDisplay } from "./utils/enhanceUrlDisplay";
import { removeFileExtension } from "./utils/removeFileExtension"; import { removeFileExtension } from "./utils/removeFileExtension";
type FeedTitleDisplayerProps = { type FeedTitleDisplayerProps = {
title: string; title: string;
truncate?: boolean; isUrl?: boolean;
}; };
export const FeedTitleDisplayer = ({ export const FeedTitleDisplayer = ({
title, title,
truncate = false, isUrl = false,
}: FeedTitleDisplayerProps): JSX.Element => { }: FeedTitleDisplayerProps): JSX.Element => {
const [showFullUrl, setShowFullUrl] = useState(false);
const toggleShowFullUrl = () => {
setShowFullUrl(!showFullUrl);
};
if (truncate) {
return (
<div className="overflow-hidden">
<span className="cursor-pointer" onClick={toggleShowFullUrl}>
<p className={showFullUrl ? "" : "truncate"}>
{removeFileExtension(title)}
</p>
</span>
</div>
);
}
return ( return (
<div> <div>
<span className="cursor-pointer" onClick={toggleShowFullUrl}> <Tooltip tooltip={title}>
{showFullUrl ? title : enhanceUrlDisplay(title)} <p className={`line-clamp-1 tooltip-${title}`}>
</span> {isUrl ? enhanceUrlDisplay(title) : removeFileExtension(title)}
</p>
</Tooltip>
</div> </div>
); );
}; };

View File

@ -0,0 +1,35 @@
import { IoMdCloseCircle } from "react-icons/io";
import { getFileIcon } from "@/lib/helpers/getFileIcon";
import { FeedTitleDisplayer } from "./FeedTitleDisplayer";
import { StyledFeedItemDiv } from "../styles/StyledFeedItemDiv";
type FileFeedItemProps = {
file: File;
onRemove: () => void;
};
export const FileFeedItem = ({
file,
onRemove,
}: FileFeedItemProps): JSX.Element => {
const icon = getFileIcon(file.name);
return (
<StyledFeedItemDiv>
<div className="flex flex-1 overflow-hidden items-center gap-1">
<div>{icon}</div>
<div className="flex flex-1">
<FeedTitleDisplayer title={file.name} />
</div>
</div>
<div>
<IoMdCloseCircle
className="cursor-pointer text-gray-400 text-lg"
onClick={onRemove}
/>
</div>
</StyledFeedItemDiv>
);
};

View File

@ -1,31 +0,0 @@
import { IoMdCloseCircle } from "react-icons/io";
import { getFileIcon } from "@/lib/helpers/getFileIcon";
import { StyledFeedItemDiv } from "../../styles/StyledFeedItemDiv";
import { FeedTitleDisplayer } from "../FeedTitleDisplayer";
type FileFeedItemProps = {
file: File;
onRemove: () => void;
};
export const FileFeedItem = ({
file,
onRemove,
}: FileFeedItemProps): JSX.Element => {
const icon = getFileIcon(file.name);
return (
<StyledFeedItemDiv>
<div className="flex flex-1 overflow-auto items-center gap-1">
{icon}
<FeedTitleDisplayer title={file.name} truncate />
</div>
<IoMdCloseCircle
className="cursor-pointer text-gray-400 text-lg"
onClick={onRemove}
/>
</StyledFeedItemDiv>
);
};

View File

@ -10,7 +10,7 @@ export const StyledFeedItemDiv = ({
<div <div
{...propsWithoutClassname} {...propsWithoutClassname}
className={cn( className={cn(
"bg-gray-100 p-4 flex flex-row items-center py-2 rounded-lg shadow-sm ", "bg-gray-100 p-4 flex flex-row items-center py-2 rounded-lg shadow-sm",
className className
)} )}
/> />

View File

@ -44,6 +44,7 @@ const Field = forwardRef(
ref={forwardedRef as RefObject<HTMLInputElement>} ref={forwardedRef as RefObject<HTMLInputElement>}
className={cn( className={cn(
`w-full bg-gray-50 dark:bg-gray-900 px-4 py-2 border rounded-md border-black/10 dark:border-white/25`, `w-full bg-gray-50 dark:bg-gray-900 px-4 py-2 border rounded-md border-black/10 dark:border-white/25`,
icon !== undefined ? "pr-12" : "",
inputClassName inputClassName
)} )}
name={name} name={name}

View File

@ -7,6 +7,8 @@ import { FeedItemType } from "@/app/chat/[chatId]/components/ActionsBar/types";
type KnowledgeToFeedContextType = { type KnowledgeToFeedContextType = {
knowledgeToFeed: FeedItemType[]; knowledgeToFeed: FeedItemType[];
setKnowledgeToFeed: React.Dispatch<React.SetStateAction<FeedItemType[]>>; setKnowledgeToFeed: React.Dispatch<React.SetStateAction<FeedItemType[]>>;
shouldDisplayFeedCard: boolean;
setShouldDisplayFeedCard: React.Dispatch<React.SetStateAction<boolean>>;
}; };
export const KnowledgeToFeedContext = createContext< export const KnowledgeToFeedContext = createContext<
@ -19,12 +21,15 @@ export const KnowledgeToFeedProvider = ({
children: React.ReactNode; children: React.ReactNode;
}): JSX.Element => { }): JSX.Element => {
const [knowledgeToFeed, setKnowledgeToFeed] = useState<FeedItemType[]>([]); const [knowledgeToFeed, setKnowledgeToFeed] = useState<FeedItemType[]>([]);
const [shouldDisplayFeedCard, setShouldDisplayFeedCard] = useState(false);
return ( return (
<KnowledgeToFeedContext.Provider <KnowledgeToFeedContext.Provider
value={{ value={{
knowledgeToFeed, knowledgeToFeed,
setKnowledgeToFeed, setKnowledgeToFeed,
shouldDisplayFeedCard,
setShouldDisplayFeedCard,
}} }}
> >
{children} {children}

View File

@ -4,14 +4,14 @@ import { useTranslation } from "react-i18next";
import { FeedItemUploadType } from "@/app/chat/[chatId]/components/ActionsBar/types"; import { FeedItemUploadType } from "@/app/chat/[chatId]/components/ActionsBar/types";
import { useEventTracking } from "@/services/analytics/june/useEventTracking"; import { useEventTracking } from "@/services/analytics/june/useEventTracking";
import { useToast } from "./useToast"; import { useToast } from "./useToast";
import { useKnowledgeToFeedContext } from "../context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; import { useKnowledgeToFeedContext } from "../context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
import { acceptedFormats } from "../helpers/acceptedFormats"; import { acceptedFormats } from "../helpers/acceptedFormats";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useCustomDropzone = () => { export const useCustomDropzone = () => {
const { knowledgeToFeed, addKnowledgeToFeed } = useKnowledgeToFeedContext(); const { knowledgeToFeed, addKnowledgeToFeed, setShouldDisplayFeedCard } =
useKnowledgeToFeedContext();
const files: File[] = ( const files: File[] = (
knowledgeToFeed.filter((c) => c.source === "upload") as FeedItemUploadType[] knowledgeToFeed.filter((c) => c.source === "upload") as FeedItemUploadType[]
@ -23,6 +23,7 @@ export const useCustomDropzone = () => {
const { t } = useTranslation(["upload"]); const { t } = useTranslation(["upload"]);
const onDrop = (acceptedFiles: File[], fileRejections: FileRejection[]) => { const onDrop = (acceptedFiles: File[], fileRejections: FileRejection[]) => {
setShouldDisplayFeedCard(true);
if (fileRejections.length > 0) { if (fileRejections.length > 0) {
const firstRejection = fileRejections[0]; const firstRejection = fileRejections[0];

View File

@ -30,7 +30,7 @@
"private_brain_label": "Private", "private_brain_label": "Private",
"public_brain_label": "Public", "public_brain_label": "Public",
"brain_status_label":"Access", "brain_status_label":"Access",
"set_brain_status_to_public_modal_title": "Are you sure you want to set this as <span class='text-purple-800'>Public</span>?<br/><br/>", "set_brain_status_to_public_modal_title": "Are you sure you want to set this as <span class='text-primary'>Public</span>?<br/><br/>",
"set_brain_status_to_public_modal_description": "Every Quivr user will be able to:<br/>- Subscribe to your brain in the 'brains library'.<br/>- Use this brain and check the prompt and model configurations.<br/><br/>They won't have access to your uploaded files and people section.", "set_brain_status_to_public_modal_description": "Every Quivr user will be able to:<br/>- Subscribe to your brain in the 'brains library'.<br/>- Use this brain and check the prompt and model configurations.<br/><br/>They won't have access to your uploaded files and people section.",
"confirm_set_brain_status_to_public": "Yes, set as public", "confirm_set_brain_status_to_public": "Yes, set as public",
"cancel_set_brain_status_to_public": "No, keep it private", "cancel_set_brain_status_to_public": "No, keep it private",
@ -41,7 +41,7 @@
"public_brain_subscription_success_message":"You have successfully subscribed to the brain", "public_brain_subscription_success_message":"You have successfully subscribed to the brain",
"public_brain_last_update_label":"Last update", "public_brain_last_update_label":"Last update",
"public_brain_already_subscribed_button_label":"Subscribed", "public_brain_already_subscribed_button_label":"Subscribed",
"set_brain_status_to_private_modal_title":"Are you sure you want to set this as <span class='text-purple-800'>Private</span>?<br/><br/>", "set_brain_status_to_private_modal_title":"Are you sure you want to set this as <span class='text-primary'>Private</span>?<br/><br/>",
"set_brain_status_to_private_modal_description":"Every Quivr users won't be able to use this brain anymore and they won't see it in the brain library.", "set_brain_status_to_private_modal_description":"Every Quivr users won't be able to use this brain anymore and they won't see it in the brain library.",
"confirm_set_brain_status_to_private":"Yes, set as private", "confirm_set_brain_status_to_private":"Yes, set as private",
"cancel_set_brain_status_to_private":"No, keep it public", "cancel_set_brain_status_to_private":"No, keep it public",

View File

@ -6,7 +6,7 @@
"success": "File uploaded successfully", "success": "File uploaded successfully",
"uploadFailed": "Failed to upload file: {{message}}", "uploadFailed": "Failed to upload file: {{message}}",
"maxSizeError": "File too big", "maxSizeError": "File too big",
"alreadyAdded": "{{fileName}} was already added", "alreadyAdded": "{{fileName}} was already added but not sent to your brain",
"addFiles": "Please, add files to upload", "addFiles": "Please, add files to upload",
"selectBrain": "Please, select or create a brain to upload a file", "selectBrain": "Please, select or create a brain to upload a file",
"invalidUrl": "Invalid URL", "invalidUrl": "Invalid URL",
@ -16,5 +16,5 @@
"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",
"feed_form_submit_button":"Send to my brain", "feed_form_submit_button":"Send to my brain",
"selected_brain_select_label":"Brain selected" "selected_brain_select_label":"Select a brain"
} }

View File

@ -30,7 +30,7 @@
"private_brain_label": "Privado", "private_brain_label": "Privado",
"public_brain_label": "Público", "public_brain_label": "Público",
"brain_status_label": "Estado", "brain_status_label": "Estado",
"set_brain_status_to_public_modal_title": "¿Estás seguro de querer establecer esto como <span class='text-purple-800'>Público</span>?<br/><br/>", "set_brain_status_to_public_modal_title": "¿Estás seguro de querer establecer esto como <span class='text-primary'>Público</span>?<br/><br/>",
"set_brain_status_to_public_modal_description": "Cada usuario de Quivr podrá:<br/>- Suscribirse a tu cerebro en la 'biblioteca de cerebros'.<br/>- Usar este cerebro y comprobar las configuraciones de las indicaciones y el modelo.<br/><br/>No tendrán acceso a tus archivos cargados ni a la sección de personas.", "set_brain_status_to_public_modal_description": "Cada usuario de Quivr podrá:<br/>- Suscribirse a tu cerebro en la 'biblioteca de cerebros'.<br/>- Usar este cerebro y comprobar las configuraciones de las indicaciones y el modelo.<br/><br/>No tendrán acceso a tus archivos cargados ni a la sección de personas.",
"confirm_set_brain_status_to_public": "Sí, establecer como público", "confirm_set_brain_status_to_public": "Sí, establecer como público",
"cancel_set_brain_status_to_public": "No, mantenerlo privado", "cancel_set_brain_status_to_public": "No, mantenerlo privado",
@ -40,7 +40,7 @@
"public_brain_subscription_success_message": "Te has suscrito con éxito al cerebro", "public_brain_subscription_success_message": "Te has suscrito con éxito al cerebro",
"public_brain_last_update_label": "Última actualización", "public_brain_last_update_label": "Última actualización",
"public_brain_already_subscribed_button_label": "Ya suscrito", "public_brain_already_subscribed_button_label": "Ya suscrito",
"set_brain_status_to_private_modal_title": "¿Estás seguro de que quieres establecer esto como <span class='text-purple-800'>Privado</span>?<br/><br/>", "set_brain_status_to_private_modal_title": "¿Estás seguro de que quieres establecer esto como <span class='text-primary'>Privado</span>?<br/><br/>",
"set_brain_status_to_private_modal_description": "Los usuarios de Quivr ya no podrán utilizar este cerebro y no lo verán en la biblioteca de cerebros.", "set_brain_status_to_private_modal_description": "Los usuarios de Quivr ya no podrán utilizar este cerebro y no lo verán en la biblioteca de cerebros.",
"confirm_set_brain_status_to_private": "Sí, establecer como privado", "confirm_set_brain_status_to_private": "Sí, establecer como privado",
"cancel_set_brain_status_to_private": "No, mantenerlo público", "cancel_set_brain_status_to_private": "No, mantenerlo público",

View File

@ -1,6 +1,6 @@
{ {
"addFiles": "Por favor, agregar archivos a subir", "addFiles": "Por favor, agregar archivos a subir",
"alreadyAdded": "{{fileName}} ya fue agregado", "alreadyAdded": "Ya se agregó {{fileName}} pero no se envió a tu cerebro.",
"crawlFailed": "Error al rastrear sitio web: {{message}}", "crawlFailed": "Error al rastrear sitio web: {{message}}",
"drop": "Suelte los archivos aquí...", "drop": "Suelte los archivos aquí...",
"uploadFailed": "Error al subir archivo: {{message}}", "uploadFailed": "Error al subir archivo: {{message}}",
@ -16,5 +16,5 @@
"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",
"feed_form_submit_button": "Enviar a mi cerebro", "feed_form_submit_button": "Enviar a mi cerebro",
"selected_brain_select_label": "Cerebro seleccionado" "selected_brain_select_label": "Seleccionar un cerebro"
} }

View File

@ -30,7 +30,7 @@
"private_brain_label": "Privé", "private_brain_label": "Privé",
"public_brain_label": "Public", "public_brain_label": "Public",
"brain_status_label": "Statut", "brain_status_label": "Statut",
"set_brain_status_to_public_modal_title": "Êtes-vous sûr de vouloir définir ceci comme <span class='text-purple-800'>Public</span>?<br/><br/>", "set_brain_status_to_public_modal_title": "Êtes-vous sûr de vouloir définir ceci comme <span class='text-primary'>Public</span>?<br/><br/>",
"set_brain_status_to_public_modal_description": "Chaque utilisateur de Quivr pourra :<br/>- S'abonner à votre cerveau dans la 'bibliothèque des cerveaux'.<br/>- Utiliser ce cerveau et vérifier les configurations de prompts et de modèles.<br/><br/>Ils n'auront pas accès à vos fichiers téléchargés et à la section des personnes.", "set_brain_status_to_public_modal_description": "Chaque utilisateur de Quivr pourra :<br/>- S'abonner à votre cerveau dans la 'bibliothèque des cerveaux'.<br/>- Utiliser ce cerveau et vérifier les configurations de prompts et de modèles.<br/><br/>Ils n'auront pas accès à vos fichiers téléchargés et à la section des personnes.",
"confirm_set_brain_status_to_public": "Oui, définir comme public", "confirm_set_brain_status_to_public": "Oui, définir comme public",
"cancel_set_brain_status_to_public": "Non, le garder privé", "cancel_set_brain_status_to_public": "Non, le garder privé",
@ -40,7 +40,7 @@
"public_brain_subscription_success_message": "Vous vous êtes abonné avec succès au cerveau", "public_brain_subscription_success_message": "Vous vous êtes abonné avec succès au cerveau",
"public_brain_last_update_label": "Dernière mise à jour", "public_brain_last_update_label": "Dernière mise à jour",
"public_brain_already_subscribed_button_label": "Abonné", "public_brain_already_subscribed_button_label": "Abonné",
"set_brain_status_to_private_modal_title": "Êtes-vous sûr de vouloir définir ceci comme <span class='text-purple-800'>Privé</span>?<br/><br/>", "set_brain_status_to_private_modal_title": "Êtes-vous sûr de vouloir définir ceci comme <span class='text-primary'>Privé</span>?<br/><br/>",
"set_brain_status_to_private_modal_description": "Les utilisateurs de Quivr ne pourront plus utiliser ce cerveau et ne le verront plus dans la bibliothèque des cerveaux.", "set_brain_status_to_private_modal_description": "Les utilisateurs de Quivr ne pourront plus utiliser ce cerveau et ne le verront plus dans la bibliothèque des cerveaux.",
"confirm_set_brain_status_to_private": "Oui, définir comme privé", "confirm_set_brain_status_to_private": "Oui, définir comme privé",
"cancel_set_brain_status_to_private": "Non, le laisser public", "cancel_set_brain_status_to_private": "Non, le laisser public",

View File

@ -6,7 +6,7 @@
"success": "Fichier téléchargé avec succès", "success": "Fichier téléchargé avec succès",
"uploadFailed": "Échec du téléchargement du fichier : {{message}}", "uploadFailed": "Échec du téléchargement du fichier : {{message}}",
"maxSizeError": "Fichier trop volumineux", "maxSizeError": "Fichier trop volumineux",
"alreadyAdded": "{{fileName}} a déjà été ajouté", "alreadyAdded": "{{fileName}} a déjà été ajouté mais n'a pas été envoyé à votre cerveau.",
"addFiles": "Veuillez ajouter des fichiers à télécharger", "addFiles": "Veuillez ajouter des fichiers à télécharger",
"selectBrain": "Veuillez sélectionner ou créer un cerveau pour télécharger un fichier", "selectBrain": "Veuillez sélectionner ou créer un cerveau pour télécharger un fichier",
"invalidUrl": "URL invalide", "invalidUrl": "URL invalide",
@ -16,5 +16,5 @@
"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",
"feed_form_submit_button":"Envoyer à mon cerveau", "feed_form_submit_button":"Envoyer à mon cerveau",
"selected_brain_select_label": "Cerveau sélectionné" "selected_brain_select_label": "Sélectionnez un cerveau"
} }

View File

@ -30,7 +30,7 @@
"private_brain_label": "Privado", "private_brain_label": "Privado",
"public_brain_label": "Público", "public_brain_label": "Público",
"brain_status_label": "Status", "brain_status_label": "Status",
"set_brain_status_to_public_modal_title": "Tem certeza de que deseja definir isso como <span class='text-purple-800'>Público</span>?<br/><br/>", "set_brain_status_to_public_modal_title": "Tem certeza de que deseja definir isso como <span class='text-primary'>Público</span>?<br/><br/>",
"set_brain_status_to_public_modal_description": "Cada usuário do Quivr poderá:<br/>- Se inscrever em seu cérebro na 'biblioteca de cérebros'.<br/>- Usar este cérebro e verificar as configurações de prompts e modelos.<br/><br/>Eles não terão acesso aos seus arquivos enviados e à seção de pessoas.", "set_brain_status_to_public_modal_description": "Cada usuário do Quivr poderá:<br/>- Se inscrever em seu cérebro na 'biblioteca de cérebros'.<br/>- Usar este cérebro e verificar as configurações de prompts e modelos.<br/><br/>Eles não terão acesso aos seus arquivos enviados e à seção de pessoas.",
"confirm_set_brain_status_to_public": "Sim, definir como público", "confirm_set_brain_status_to_public": "Sim, definir como público",
"cancel_set_brain_status_to_public": "Não, mantê-lo privado", "cancel_set_brain_status_to_public": "Não, mantê-lo privado",
@ -40,7 +40,7 @@
"public_brain_subscription_success_message": "Você se inscreveu com sucesso no cérebro", "public_brain_subscription_success_message": "Você se inscreveu com sucesso no cérebro",
"public_brain_last_update_label": "Última atualização", "public_brain_last_update_label": "Última atualização",
"public_brain_already_subscribed_button_label": "Inscrito", "public_brain_already_subscribed_button_label": "Inscrito",
"set_brain_status_to_private_modal_title": "Tem a certeza de que deseja definir isto como <span class='text-purple-800'>Privado</span>?<br/><br/>", "set_brain_status_to_private_modal_title": "Tem a certeza de que deseja definir isto como <span class='text-primary'>Privado</span>?<br/><br/>",
"set_brain_status_to_private_modal_description": "Os utilizadores do Quivr não poderão mais utilizar este cérebro e não o verão na biblioteca de cérebros.", "set_brain_status_to_private_modal_description": "Os utilizadores do Quivr não poderão mais utilizar este cérebro e não o verão na biblioteca de cérebros.",
"confirm_set_brain_status_to_private": "Sim, definir como privado", "confirm_set_brain_status_to_private": "Sim, definir como privado",
"cancel_set_brain_status_to_private": "Não, mantê-lo público", "cancel_set_brain_status_to_private": "Não, mantê-lo público",

View File

@ -6,7 +6,7 @@
"success": "Arquivo enviado com sucesso", "success": "Arquivo enviado com sucesso",
"uploadFailed": "Falha ao enviar arquivo: {{message}}", "uploadFailed": "Falha ao enviar arquivo: {{message}}",
"maxSizeError": "Arquivo muito grande", "maxSizeError": "Arquivo muito grande",
"alreadyAdded": "{{fileName}} já foi adicionado", "alreadyAdded": "{{fileName}} já foi adicionado, mas não foi enviado para o seu cérebro.",
"addFiles": "Por favor, adicione arquivos para enviar", "addFiles": "Por favor, adicione arquivos para enviar",
"selectBrain": "Por favor, selecione ou crie um cérebro para enviar um arquivo", "selectBrain": "Por favor, selecione ou crie um cérebro para enviar um arquivo",
"invalidUrl": "URL inválido", "invalidUrl": "URL inválido",
@ -16,5 +16,5 @@
"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",
"feed_form_submit_button": "Enviar para o meu cérebro", "feed_form_submit_button": "Enviar para o meu cérebro",
"selected_brain_select_label": "Cérebro selecionado" "selected_brain_select_label": "Selecione um cérebro"
} }

View File

@ -30,7 +30,7 @@
"private_brain_label": "Приватный", "private_brain_label": "Приватный",
"public_brain_label": "Публичный", "public_brain_label": "Публичный",
"brain_status_label": "Статус", "brain_status_label": "Статус",
"set_brain_status_to_public_modal_title": "Вы уверены, что хотите установить это как <span class='text-purple-800'>Публичный</span>?<br/><br/>", "set_brain_status_to_public_modal_title": "Вы уверены, что хотите установить это как <span class='text-primary'>Публичный</span>?<br/><br/>",
"set_brain_status_to_public_modal_description": "Каждый пользователь Quivr сможет:<br/>- Подписаться на ваш мозг в 'библиотеке мозгов'.<br/>- Использовать этот мозг и проверить настройки подсказок и модели.<br/><br/>У них не будет доступа к вашим загруженным файлам и разделу 'люди'.", "set_brain_status_to_public_modal_description": "Каждый пользователь Quivr сможет:<br/>- Подписаться на ваш мозг в 'библиотеке мозгов'.<br/>- Использовать этот мозг и проверить настройки подсказок и модели.<br/><br/>У них не будет доступа к вашим загруженным файлам и разделу 'люди'.",
"confirm_set_brain_status_to_public": "Да, установить как публичный", "confirm_set_brain_status_to_public": "Да, установить как публичный",
"cancel_set_brain_status_to_public": "Нет, оставить приватным", "cancel_set_brain_status_to_public": "Нет, оставить приватным",
@ -40,7 +40,7 @@
"public_brain_subscription_success_message": "Вы успешно подписались на мозг", "public_brain_subscription_success_message": "Вы успешно подписались на мозг",
"public_brain_last_update_label": "Последнее обновление", "public_brain_last_update_label": "Последнее обновление",
"public_brain_already_subscribed_button_label": "Вы уже подписаны", "public_brain_already_subscribed_button_label": "Вы уже подписаны",
"set_brain_status_to_private_modal_title": "Вы уверены, что хотите установить это как <span class='text-purple-800'>Частное</span>?<br/><br/>", "set_brain_status_to_private_modal_title": "Вы уверены, что хотите установить это как <span class='text-primary'>Частное</span>?<br/><br/>",
"set_brain_status_to_private_modal_description": "Пользователи Quivr больше не смогут использовать этот мозг, и они не увидят его в библиотеке мозгов.", "set_brain_status_to_private_modal_description": "Пользователи Quivr больше не смогут использовать этот мозг, и они не увидят его в библиотеке мозгов.",
"confirm_set_brain_status_to_private": "Да, установить как частное", "confirm_set_brain_status_to_private": "Да, установить как частное",
"cancel_set_brain_status_to_private": "Нет, оставить общедоступным", "cancel_set_brain_status_to_private": "Нет, оставить общедоступным",

View File

@ -6,7 +6,7 @@
"success": "Файл успешно загружен", "success": "Файл успешно загружен",
"uploadFailed": "Не удалось загрузить файл: {{message}}", "uploadFailed": "Не удалось загрузить файл: {{message}}",
"maxSizeError": "Файл слишком большой", "maxSizeError": "Файл слишком большой",
"alreadyAdded": "{{fileName}} уже добавлен", "alreadyAdded": "{{fileName}} уже добавлен, но не отправлен в ваш мозг.",
"addFiles": "Пожалуйста, добавьте файлы для загрузки", "addFiles": "Пожалуйста, добавьте файлы для загрузки",
"selectBrain": "Пожалуйста, выберите или создайте мозг для загрузки файла", "selectBrain": "Пожалуйста, выберите или создайте мозг для загрузки файла",
"invalidUrl": "Неверный URL", "invalidUrl": "Неверный URL",
@ -16,5 +16,5 @@
"missingNecessaryRole": "У вас нет необходимой роли для загрузки контента в выбранный мозг. 🧠💡🥲", "missingNecessaryRole": "У вас нет необходимой роли для загрузки контента в выбранный мозг. 🧠💡🥲",
"invalidFileType": "Неверный тип файла", "invalidFileType": "Неверный тип файла",
"feed_form_submit_button": "Отправить в мой мозг", "feed_form_submit_button": "Отправить в мой мозг",
"selected_brain_select_label": "Выбран мозг" "selected_brain_select_label": "Выберите мозг"
} }

View File

@ -30,7 +30,7 @@
"private_brain_label": "私有", "private_brain_label": "私有",
"public_brain_label": "公开", "public_brain_label": "公开",
"brain_status_label": "状态", "brain_status_label": "状态",
"set_brain_status_to_public_modal_title": "您确定要将此设置为<span class='text-purple-800'>公共</span>吗?<br/><br/>", "set_brain_status_to_public_modal_title": "您确定要将此设置为<span class='text-primary'>公共</span>吗?<br/><br/>",
"set_brain_status_to_public_modal_description": "每个 Quivr 用户将能够:<br/>- 在 '大脑库' 中订阅您的大脑。<br/>- 使用此大脑并检查提示和模型配置。<br/><br/>他们将无法访问您上传的文件和人员部分。", "set_brain_status_to_public_modal_description": "每个 Quivr 用户将能够:<br/>- 在 '大脑库' 中订阅您的大脑。<br/>- 使用此大脑并检查提示和模型配置。<br/><br/>他们将无法访问您上传的文件和人员部分。",
"confirm_set_brain_status_to_public": "是的,设为公共", "confirm_set_brain_status_to_public": "是的,设为公共",
"cancel_set_brain_status_to_public": "不,保持私密", "cancel_set_brain_status_to_public": "不,保持私密",
@ -40,7 +40,7 @@
"public_brain_subscription_success_message": "Вы успешно подписались на мозг", "public_brain_subscription_success_message": "Вы успешно подписались на мозг",
"public_brain_last_update_label": "Последнее обновление", "public_brain_last_update_label": "Последнее обновление",
"public_brain_already_subscribed_button_label": "已订阅", "public_brain_already_subscribed_button_label": "已订阅",
"set_brain_status_to_private_modal_title": "您确定要将此设置为<span class='text-purple-800'>私有</span>吗?<br/><br/>", "set_brain_status_to_private_modal_title": "您确定要将此设置为<span class='text-primary'>私有</span>吗?<br/><br/>",
"set_brain_status_to_private_modal_description": "Quivr的用户将无法再使用此大脑并且不会在大脑库中看到它。", "set_brain_status_to_private_modal_description": "Quivr的用户将无法再使用此大脑并且不会在大脑库中看到它。",
"confirm_set_brain_status_to_private": "是的,设为私有", "confirm_set_brain_status_to_private": "是的,设为私有",
"cancel_set_brain_status_to_private": "不,保持为公开", "cancel_set_brain_status_to_private": "不,保持为公开",

View File

@ -6,7 +6,7 @@
"success": "文件上传成功", "success": "文件上传成功",
"uploadFailed": "上传文件失败:{{message}}", "uploadFailed": "上传文件失败:{{message}}",
"maxSizeError": "文件太大", "maxSizeError": "文件太大",
"alreadyAdded": "已添加 {{filename}}", "alreadyAdded": "{{fileName}} 已经添加,但尚未发送到您的大脑。",
"addFiles": "请添加要上传的文件", "addFiles": "请添加要上传的文件",
"selectBrain": "请选择或创建一个大脑来上传文件。", "selectBrain": "请选择或创建一个大脑来上传文件。",
"invalidUrl": "无效的URL", "invalidUrl": "无效的URL",
@ -16,5 +16,5 @@
"missingNecessaryRole": "您没有所选大脑的上传权限。 🧠💡🥲", "missingNecessaryRole": "您没有所选大脑的上传权限。 🧠💡🥲",
"invalidFileType": "文件类型不受支持", "invalidFileType": "文件类型不受支持",
"feed_form_submit_button":"发送到我的大脑", "feed_form_submit_button":"发送到我的大脑",
"selected_brain_select_label": "已选择的大脑" "selected_brain_select_label": "选择一个大脑"
} }

View File

@ -17,6 +17,9 @@ module.exports = {
colors: { colors: {
black: "#00121F", black: "#00121F",
primary: "#4F46E5", primary: "#4F46E5",
"chat-bg-gray": "#D9D9D9",
"msg-gray": "#9B9B9B",
"msg-purple": "#E0DDFC",
}, },
}, },
}, },