mirror of
https://github.com/StanGirard/quivr.git
synced 2024-12-25 04:12:44 +03:00
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:
parent
72ef62aad0
commit
36fd146fed
@ -88,7 +88,7 @@ export const SettingsTab = ({ brainId }: SettingsTabProps): JSX.Element => {
|
||||
<div className="mt-4">
|
||||
<div className="flex flex-1 items-center flex-col">
|
||||
{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")}
|
||||
</Chip>
|
||||
)}
|
||||
|
@ -71,7 +71,7 @@ export const BrainsList = (): JSX.Element => {
|
||||
>
|
||||
<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")}
|
||||
</Button>
|
||||
@ -83,7 +83,7 @@ export const BrainsList = (): JSX.Element => {
|
||||
>
|
||||
<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")}
|
||||
</Button>
|
||||
|
@ -36,7 +36,7 @@ export const PublicBrainItem = ({
|
||||
}}
|
||||
disabled={isUserSubscribedToBrain || 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
|
||||
? t("public_brain_already_subscribed_button_label")
|
||||
|
@ -2,21 +2,16 @@ import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AiOutlineLoading3Quarters } from "react-icons/ai";
|
||||
|
||||
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
||||
|
||||
import { ChatInput, KnowledgeToFeed } from "./components";
|
||||
import { useActionBar } from "./hooks/useActionBar";
|
||||
|
||||
type ActionBarProps = {
|
||||
setShouldDisplayUploadCard: (shouldDisplay: boolean) => void;
|
||||
shouldDisplayUploadCard: boolean;
|
||||
};
|
||||
|
||||
export const ActionsBar = ({
|
||||
setShouldDisplayUploadCard,
|
||||
shouldDisplayUploadCard,
|
||||
}: ActionBarProps): JSX.Element => {
|
||||
export const ActionsBar = (): JSX.Element => {
|
||||
const { hasPendingRequests, setHasPendingRequests } = useActionBar();
|
||||
|
||||
const { t } = useTranslation(["chat"]);
|
||||
const { shouldDisplayFeedCard } = useKnowledgeToFeedContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -30,7 +25,7 @@ export const ActionsBar = ({
|
||||
)}
|
||||
|
||||
<div>
|
||||
{shouldDisplayUploadCard && (
|
||||
{shouldDisplayFeedCard && (
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
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">
|
||||
<KnowledgeToFeed
|
||||
closeFeedInput={() => setShouldDisplayUploadCard(false)}
|
||||
dispatchHasPendingRequests={() => setHasPendingRequests(true)}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</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">
|
||||
<ChatInput
|
||||
shouldDisplayUploadCard={shouldDisplayUploadCard}
|
||||
setShouldDisplayUploadCard={setShouldDisplayUploadCard}
|
||||
/>
|
||||
<ChatInput shouldDisplayFeedCard={shouldDisplayFeedCard} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -3,24 +3,25 @@ import { useTranslation } from "react-i18next";
|
||||
import { PiPaperclipFill } from "react-icons/pi";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
||||
|
||||
import { ChatBar } from "./components/ChatBar/ChatBar";
|
||||
import { ConfigModal } from "./components/ConfigModal";
|
||||
import { useChatInput } from "./hooks/useChatInput";
|
||||
|
||||
type ChatInputProps = {
|
||||
shouldDisplayUploadCard: boolean;
|
||||
setShouldDisplayUploadCard: (shouldDisplayUploadCard: boolean) => void;
|
||||
shouldDisplayFeedCard: boolean;
|
||||
};
|
||||
|
||||
export const ChatInput = ({
|
||||
shouldDisplayUploadCard,
|
||||
setShouldDisplayUploadCard,
|
||||
shouldDisplayFeedCard,
|
||||
}: ChatInputProps): JSX.Element => {
|
||||
const { setMessage, submitQuestion, generatingAnswer, message } =
|
||||
useChatInput();
|
||||
const { t } = useTranslation(["chat"]);
|
||||
|
||||
const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext();
|
||||
|
||||
return (
|
||||
<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"
|
||||
>
|
||||
{!shouldDisplayUploadCard && (
|
||||
{!shouldDisplayFeedCard && (
|
||||
<Button
|
||||
className="p-0"
|
||||
variant={"tertiary"}
|
||||
data-testid="upload-button"
|
||||
type="button"
|
||||
onClick={() => setShouldDisplayUploadCard(true)}
|
||||
onClick={() => setShouldDisplayFeedCard(true)}
|
||||
tooltip={t("add_content_card_button_tooltip")}
|
||||
>
|
||||
<PiPaperclipFill className="text-3xl" />
|
||||
|
@ -8,22 +8,23 @@ import Button from "@/lib/components/ui/Button";
|
||||
import { Select } from "@/lib/components/ui/Select";
|
||||
import { requiredRolesForUpload } from "@/lib/config/upload";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
||||
|
||||
import { useFeedBrainInChat } from "./hooks/useFeedBrainInChat";
|
||||
import { formatMinimalBrainsToSelectComponentInput } from "./utils/formatMinimalBrainsToSelectComponentInput";
|
||||
|
||||
type KnowledgeToFeedProps = {
|
||||
closeFeedInput: () => void;
|
||||
dispatchHasPendingRequests?: () => void;
|
||||
dispatchHasPendingRequests: () => void;
|
||||
};
|
||||
export const KnowledgeToFeed = ({
|
||||
closeFeedInput,
|
||||
dispatchHasPendingRequests,
|
||||
}: KnowledgeToFeedProps): JSX.Element => {
|
||||
const { allBrains, currentBrainId, setCurrentBrainId } = useBrainContext();
|
||||
|
||||
const { t } = useTranslation(["upload"]);
|
||||
|
||||
const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext();
|
||||
|
||||
const brainsWithUploadRights = useMemo(
|
||||
() =>
|
||||
allBrains.filter((brain) => requiredRolesForUpload.includes(brain.role)),
|
||||
@ -32,14 +33,16 @@ export const KnowledgeToFeed = ({
|
||||
|
||||
const { feedBrain } = useFeedBrainInChat({
|
||||
dispatchHasPendingRequests,
|
||||
closeFeedInput,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex-col w-full relative">
|
||||
<div className="flex flex-1 justify-between">
|
||||
<AddBrainModal />
|
||||
<Button variant={"tertiary"} onClick={closeFeedInput}>
|
||||
<Button
|
||||
variant={"tertiary"}
|
||||
onClick={() => setShouldDisplayFeedCard(false)}
|
||||
>
|
||||
<span>
|
||||
<MdClose className="text-3xl" />
|
||||
</span>
|
||||
@ -47,10 +50,10 @@ export const KnowledgeToFeed = ({
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Select
|
||||
label={t("selected_brain_select_label")}
|
||||
options={formatMinimalBrainsToSelectComponentInput(
|
||||
brainsWithUploadRights
|
||||
)}
|
||||
emptyLabel={t("selected_brain_select_label")}
|
||||
value={currentBrainId ?? undefined}
|
||||
onChange={(newSelectedBrainId) =>
|
||||
setCurrentBrainId(newSelectedBrainId)
|
||||
|
@ -16,14 +16,13 @@ import { FeedItemCrawlType, FeedItemUploadType } from "../../../types";
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useFeedBrainInChat = ({
|
||||
dispatchHasPendingRequests,
|
||||
closeFeedInput,
|
||||
}: {
|
||||
dispatchHasPendingRequests?: () => void;
|
||||
closeFeedInput?: () => void;
|
||||
dispatchHasPendingRequests: () => void;
|
||||
}) => {
|
||||
const { publish } = useToast();
|
||||
const { t } = useTranslation(["upload"]);
|
||||
const router = useRouter();
|
||||
const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext();
|
||||
|
||||
const { currentBrainId } = useBrainContext();
|
||||
const { setKnowledgeToFeed, knowledgeToFeed } = useKnowledgeToFeedContext();
|
||||
@ -62,8 +61,8 @@ export const useFeedBrainInChat = ({
|
||||
return;
|
||||
}
|
||||
try {
|
||||
dispatchHasPendingRequests?.();
|
||||
closeFeedInput?.();
|
||||
dispatchHasPendingRequests();
|
||||
setShouldDisplayFeedCard(false);
|
||||
setHasPendingRequests(true);
|
||||
const currentChatId = chatId ?? (await createChat("New Chat")).chat_id;
|
||||
const uploadPromises = files.map((file) =>
|
||||
|
@ -25,16 +25,16 @@ export const MessageRow = React.forwardRef(
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(text).then(
|
||||
() => 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(
|
||||
"py-3 px-5 w-fit",
|
||||
isUserSpeaker
|
||||
? "bg-gray-100 bg-opacity-60 items-start"
|
||||
: "bg-purple-100 bg-opacity-60 items-end",
|
||||
? "bg-msg-gray bg-opacity-60 items-start"
|
||||
: "bg-msg-purple bg-opacity-60 items-end",
|
||||
"dark:bg-gray-800 rounded-3xl flex flex-col overflow-hidden scroll-pb-32"
|
||||
);
|
||||
|
||||
|
@ -2,6 +2,7 @@ import _debounce from "lodash/debounce";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
|
||||
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
|
||||
const chatInputHeightEstimation = 100;
|
||||
@ -10,6 +11,7 @@ const chatInputHeightEstimation = 100;
|
||||
export const useChatDialogue = () => {
|
||||
const chatListRef = useRef<HTMLDivElement | null>(null);
|
||||
const { messages } = useChat();
|
||||
const { shouldDisplayFeedCard } = useKnowledgeToFeedContext();
|
||||
|
||||
const scrollToBottom = useCallback(
|
||||
_debounce(() => {
|
||||
@ -43,7 +45,7 @@ export const useChatDialogue = () => {
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [messages, scrollToBottom]);
|
||||
}, [messages, scrollToBottom, shouldDisplayFeedCard]);
|
||||
|
||||
return {
|
||||
chatListRef,
|
||||
|
@ -22,6 +22,7 @@ export const ChatDialogue = ({
|
||||
flexDirection: "column",
|
||||
flex: 1,
|
||||
overflowY: "auto",
|
||||
marginBottom: 10,
|
||||
}}
|
||||
ref={chatListRef}
|
||||
>
|
||||
|
@ -10,20 +10,19 @@ export const ChatHeader = (): JSX.Element => {
|
||||
return (
|
||||
<h1 className="hidden lg:block text-3xl font-bold text-center">
|
||||
{t("chat_title_intro")}{" "}
|
||||
<span className="text-purple-500">{t("brains")}</span>
|
||||
<span className="text-primary">{t("brains")}</span>
|
||||
</h1>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<h1 className="hidden lg:block text-3xl font-bold text-center">
|
||||
{t("chat_title_intro")}{" "}
|
||||
|
||||
<span className="text-purple-500">{t("brains")}</span>
|
||||
<span className="text-primary">{t("brains")}</span>
|
||||
{" !! "}
|
||||
<br />
|
||||
{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")}
|
||||
</h1>
|
||||
);
|
||||
|
@ -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,
|
||||
};
|
||||
};
|
@ -1,16 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
||||
import { useCustomDropzone } from "@/lib/hooks/useDropzone";
|
||||
|
||||
import { ActionsBar } from "./components/ActionsBar";
|
||||
import { ChatDialogueArea } from "./components/ChatDialogueArea/ChatDialogue";
|
||||
import { ChatHeader } from "./components/ChatHeader";
|
||||
import { useSelectedChatPage } from "./hooks/useSelectedChatPage";
|
||||
|
||||
const SelectedChatPage = (): JSX.Element => {
|
||||
const { setShouldDisplayUploadCard, shouldDisplayUploadCard } =
|
||||
useSelectedChatPage();
|
||||
const { getRootProps } = useCustomDropzone();
|
||||
const { shouldDisplayFeedCard } = useKnowledgeToFeedContext();
|
||||
|
||||
return (
|
||||
<main
|
||||
@ -22,16 +21,13 @@ const SelectedChatPage = (): JSX.Element => {
|
||||
<ChatHeader />
|
||||
<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 ${
|
||||
shouldDisplayUploadCard ? "bg-gray-100" : "bg-white"
|
||||
shouldDisplayFeedCard ? "bg-chat-bg-gray" : "bg-white"
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-1 flex-col overflow-y-auto">
|
||||
<ChatDialogueArea />
|
||||
</div>
|
||||
<ActionsBar
|
||||
setShouldDisplayUploadCard={setShouldDisplayUploadCard}
|
||||
shouldDisplayUploadCard={shouldDisplayUploadCard}
|
||||
/>
|
||||
<ActionsBar />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
@ -11,7 +11,9 @@ import {
|
||||
ChatContextMock,
|
||||
ChatProviderMock,
|
||||
} from "@/lib/context/ChatProvider/mocks/ChatProviderMock";
|
||||
import { KnowledgeToFeedProvider } from "@/lib/context/KnowledgeToFeedProvider";
|
||||
import { SupabaseContextMock } from "@/lib/context/SupabaseProvider/mocks/SupabaseProviderMock";
|
||||
|
||||
vi.mock("@/lib/context/SupabaseProvider/supabase-provider", () => ({
|
||||
SupabaseContext: SupabaseContextMock,
|
||||
}));
|
||||
@ -89,11 +91,13 @@ describe("ChatsList", () => {
|
||||
it("should render correctly", () => {
|
||||
const { getByTestId } = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ChatProviderMock>
|
||||
<BrainProviderMock>
|
||||
<ChatsList />
|
||||
</BrainProviderMock>
|
||||
</ChatProviderMock>
|
||||
<KnowledgeToFeedProvider>
|
||||
<ChatProviderMock>
|
||||
<BrainProviderMock>
|
||||
<ChatsList />
|
||||
</BrainProviderMock>
|
||||
</ChatProviderMock>
|
||||
</KnowledgeToFeedProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
const chatsList = getByTestId("chats-list");
|
||||
@ -109,11 +113,13 @@ describe("ChatsList", () => {
|
||||
it("renders the chats list with correct number of items", () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ChatProviderMock>
|
||||
<BrainProviderMock>
|
||||
<ChatsList />
|
||||
</BrainProviderMock>
|
||||
</ChatProviderMock>
|
||||
<KnowledgeToFeedProvider>
|
||||
<ChatProviderMock>
|
||||
<BrainProviderMock>
|
||||
<ChatsList />
|
||||
</BrainProviderMock>
|
||||
</ChatProviderMock>
|
||||
</KnowledgeToFeedProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
const chatItems = screen.getAllByTestId("chats-list-item");
|
||||
@ -129,11 +135,13 @@ describe("ChatsList", () => {
|
||||
await act(() =>
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ChatProviderMock>
|
||||
<BrainProviderMock>
|
||||
<ChatsList />
|
||||
</BrainProviderMock>
|
||||
</ChatProviderMock>
|
||||
<KnowledgeToFeedProvider>
|
||||
<ChatProviderMock>
|
||||
<BrainProviderMock>
|
||||
<ChatsList />
|
||||
</BrainProviderMock>
|
||||
</ChatProviderMock>
|
||||
</KnowledgeToFeedProvider>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
);
|
||||
@ -155,11 +163,13 @@ describe("ChatsList", () => {
|
||||
await act(() =>
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ChatProviderMock>
|
||||
<BrainProviderMock>
|
||||
<ChatsList />
|
||||
</BrainProviderMock>
|
||||
</ChatProviderMock>
|
||||
<KnowledgeToFeedProvider>
|
||||
<ChatProviderMock>
|
||||
<BrainProviderMock>
|
||||
<ChatsList />
|
||||
</BrainProviderMock>
|
||||
</ChatProviderMock>
|
||||
</KnowledgeToFeedProvider>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
);
|
||||
|
@ -5,6 +5,7 @@ import { useEffect } from "react";
|
||||
import { useChatApi } from "@/lib/api/chat/useChatApi";
|
||||
import { useNotificationApi } from "@/lib/api/notification/useNotificationApi";
|
||||
import { useChatContext } from "@/lib/context";
|
||||
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
||||
|
||||
import { getChatNotificationsQueryKey } from "../../../[chatId]/utils/getChatNotificationsQueryKey";
|
||||
import { getMessagesFromChatItems } from "../../../[chatId]/utils/getMessagesFromChatItems";
|
||||
@ -15,6 +16,7 @@ export const useChatNotificationsSync = () => {
|
||||
const { setMessages, setNotifications, notifications } = useChatContext();
|
||||
const { getChatItems } = useChatApi();
|
||||
const { getChatNotifications } = useNotificationApi();
|
||||
const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext();
|
||||
const params = useParams();
|
||||
|
||||
const chatId = params?.chatId as string | undefined;
|
||||
@ -54,6 +56,7 @@ export const useChatNotificationsSync = () => {
|
||||
}, [fetchedNotifications]);
|
||||
|
||||
useEffect(() => {
|
||||
setShouldDisplayFeedCard(false);
|
||||
const fetchHistory = async () => {
|
||||
if (chatId === undefined) {
|
||||
setMessages([]);
|
||||
|
@ -31,7 +31,7 @@ export const KnowledgeToFeedInput = ({
|
||||
<div className="flex justify-center mt-5">
|
||||
<Button
|
||||
disabled={knowledgeToFeed.length === 0}
|
||||
className="rounded-xl bg-purple-600 border-white"
|
||||
className="rounded-xl bg-primary border-white"
|
||||
onClick={() => void feedBrain()}
|
||||
>
|
||||
{t("feed_form_submit_button", { ns: "upload" })}
|
||||
|
@ -3,7 +3,7 @@ import { Fragment } from "react";
|
||||
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
||||
|
||||
import { CrawlFeedItem } from "./components/CrawlFeedItem";
|
||||
import { FileFeedItem } from "./components/FileFeedItem/FileFeedItem";
|
||||
import { FileFeedItem } from "./components/FileFeedItem";
|
||||
|
||||
export const FeedItems = (): JSX.Element => {
|
||||
const { knowledgeToFeed, removeKnowledgeToFeed } =
|
||||
|
@ -14,14 +14,20 @@ export const CrawlFeedItem = ({
|
||||
}: CrawlFeedItemProps): JSX.Element => {
|
||||
return (
|
||||
<StyledFeedItemDiv>
|
||||
<div className="flex flex-1 items-center">
|
||||
<MdLink className="mr-2 text-2xl" />
|
||||
<FeedTitleDisplayer title={url} />
|
||||
<div className="flex flex-1 overflow-hidden items-center gap-1">
|
||||
<div>
|
||||
<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>
|
||||
<IoMdCloseCircle
|
||||
className="cursor-pointer text-gray-400 text-lg"
|
||||
onClick={onRemove}
|
||||
/>
|
||||
</StyledFeedItemDiv>
|
||||
);
|
||||
};
|
||||
|
@ -1,40 +1,24 @@
|
||||
import { useState } from "react";
|
||||
import Tooltip from "@/lib/components/ui/Tooltip";
|
||||
|
||||
import { enhanceUrlDisplay } from "./utils/enhanceUrlDisplay";
|
||||
import { removeFileExtension } from "./utils/removeFileExtension";
|
||||
|
||||
type FeedTitleDisplayerProps = {
|
||||
title: string;
|
||||
truncate?: boolean;
|
||||
isUrl?: boolean;
|
||||
};
|
||||
|
||||
export const FeedTitleDisplayer = ({
|
||||
title,
|
||||
truncate = false,
|
||||
isUrl = false,
|
||||
}: 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 (
|
||||
<div>
|
||||
<span className="cursor-pointer" onClick={toggleShowFullUrl}>
|
||||
{showFullUrl ? title : enhanceUrlDisplay(title)}
|
||||
</span>
|
||||
<Tooltip tooltip={title}>
|
||||
<p className={`line-clamp-1 tooltip-${title}`}>
|
||||
{isUrl ? enhanceUrlDisplay(title) : removeFileExtension(title)}
|
||||
</p>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -1 +0,0 @@
|
||||
export * from "./FileFeedItem";
|
@ -10,7 +10,7 @@ export const StyledFeedItemDiv = ({
|
||||
<div
|
||||
{...propsWithoutClassname}
|
||||
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
|
||||
)}
|
||||
/>
|
||||
|
@ -44,6 +44,7 @@ const Field = forwardRef(
|
||||
ref={forwardedRef as RefObject<HTMLInputElement>}
|
||||
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`,
|
||||
icon !== undefined ? "pr-12" : "",
|
||||
inputClassName
|
||||
)}
|
||||
name={name}
|
||||
|
@ -7,6 +7,8 @@ import { FeedItemType } from "@/app/chat/[chatId]/components/ActionsBar/types";
|
||||
type KnowledgeToFeedContextType = {
|
||||
knowledgeToFeed: FeedItemType[];
|
||||
setKnowledgeToFeed: React.Dispatch<React.SetStateAction<FeedItemType[]>>;
|
||||
shouldDisplayFeedCard: boolean;
|
||||
setShouldDisplayFeedCard: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
export const KnowledgeToFeedContext = createContext<
|
||||
@ -19,12 +21,15 @@ export const KnowledgeToFeedProvider = ({
|
||||
children: React.ReactNode;
|
||||
}): JSX.Element => {
|
||||
const [knowledgeToFeed, setKnowledgeToFeed] = useState<FeedItemType[]>([]);
|
||||
const [shouldDisplayFeedCard, setShouldDisplayFeedCard] = useState(false);
|
||||
|
||||
return (
|
||||
<KnowledgeToFeedContext.Provider
|
||||
value={{
|
||||
knowledgeToFeed,
|
||||
setKnowledgeToFeed,
|
||||
shouldDisplayFeedCard,
|
||||
setShouldDisplayFeedCard,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@ -4,14 +4,14 @@ import { useTranslation } from "react-i18next";
|
||||
import { FeedItemUploadType } from "@/app/chat/[chatId]/components/ActionsBar/types";
|
||||
import { useEventTracking } from "@/services/analytics/june/useEventTracking";
|
||||
|
||||
|
||||
import { useToast } from "./useToast";
|
||||
import { useKnowledgeToFeedContext } from "../context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
||||
import { acceptedFormats } from "../helpers/acceptedFormats";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useCustomDropzone = () => {
|
||||
const { knowledgeToFeed, addKnowledgeToFeed } = useKnowledgeToFeedContext();
|
||||
const { knowledgeToFeed, addKnowledgeToFeed, setShouldDisplayFeedCard } =
|
||||
useKnowledgeToFeedContext();
|
||||
|
||||
const files: File[] = (
|
||||
knowledgeToFeed.filter((c) => c.source === "upload") as FeedItemUploadType[]
|
||||
@ -23,6 +23,7 @@ export const useCustomDropzone = () => {
|
||||
const { t } = useTranslation(["upload"]);
|
||||
|
||||
const onDrop = (acceptedFiles: File[], fileRejections: FileRejection[]) => {
|
||||
setShouldDisplayFeedCard(true);
|
||||
if (fileRejections.length > 0) {
|
||||
const firstRejection = fileRejections[0];
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
"private_brain_label": "Private",
|
||||
"public_brain_label": "Public",
|
||||
"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.",
|
||||
"confirm_set_brain_status_to_public": "Yes, set as public",
|
||||
"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_last_update_label":"Last update",
|
||||
"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.",
|
||||
"confirm_set_brain_status_to_private":"Yes, set as private",
|
||||
"cancel_set_brain_status_to_private":"No, keep it public",
|
||||
|
@ -6,7 +6,7 @@
|
||||
"success": "File uploaded successfully",
|
||||
"uploadFailed": "Failed to upload file: {{message}}",
|
||||
"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",
|
||||
"selectBrain": "Please, select or create a brain to upload a file",
|
||||
"invalidUrl": "Invalid URL",
|
||||
@ -16,5 +16,5 @@
|
||||
"missingNecessaryRole": "You don't have the necessary role to upload content to the selected brain. 🧠💡🥲",
|
||||
"invalidFileType": "Invalid file type",
|
||||
"feed_form_submit_button":"Send to my brain",
|
||||
"selected_brain_select_label":"Brain selected"
|
||||
"selected_brain_select_label":"Select a brain"
|
||||
}
|
||||
|
@ -30,7 +30,7 @@
|
||||
"private_brain_label": "Privado",
|
||||
"public_brain_label": "Público",
|
||||
"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.",
|
||||
"confirm_set_brain_status_to_public": "Sí, establecer como público",
|
||||
"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_last_update_label": "Última actualización",
|
||||
"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.",
|
||||
"confirm_set_brain_status_to_private": "Sí, establecer como privado",
|
||||
"cancel_set_brain_status_to_private": "No, mantenerlo público",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"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}}",
|
||||
"drop": "Suelte los archivos aquí...",
|
||||
"uploadFailed": "Error al subir archivo: {{message}}",
|
||||
@ -16,5 +16,5 @@
|
||||
"webSite": "Ingrese la URL del sitio web",
|
||||
"invalidFileType": "Tipo de archivo no permitido",
|
||||
"feed_form_submit_button": "Enviar a mi cerebro",
|
||||
"selected_brain_select_label": "Cerebro seleccionado"
|
||||
"selected_brain_select_label": "Seleccionar un cerebro"
|
||||
}
|
@ -30,7 +30,7 @@
|
||||
"private_brain_label": "Privé",
|
||||
"public_brain_label": "Public",
|
||||
"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.",
|
||||
"confirm_set_brain_status_to_public": "Oui, définir comme public",
|
||||
"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_last_update_label": "Dernière mise à jour",
|
||||
"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.",
|
||||
"confirm_set_brain_status_to_private": "Oui, définir comme privé",
|
||||
"cancel_set_brain_status_to_private": "Non, le laisser public",
|
||||
|
@ -6,7 +6,7 @@
|
||||
"success": "Fichier téléchargé avec succès",
|
||||
"uploadFailed": "Échec du téléchargement du fichier : {{message}}",
|
||||
"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",
|
||||
"selectBrain": "Veuillez sélectionner ou créer un cerveau pour télécharger un fichier",
|
||||
"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é. 🧠💡🥲",
|
||||
"invalidFileType": "Type de fichier invalide",
|
||||
"feed_form_submit_button":"Envoyer à mon cerveau",
|
||||
"selected_brain_select_label": "Cerveau sélectionné"
|
||||
"selected_brain_select_label": "Sélectionnez un cerveau"
|
||||
}
|
@ -30,7 +30,7 @@
|
||||
"private_brain_label": "Privado",
|
||||
"public_brain_label": "Público",
|
||||
"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.",
|
||||
"confirm_set_brain_status_to_public": "Sim, definir como público",
|
||||
"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_last_update_label": "Última atualização",
|
||||
"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.",
|
||||
"confirm_set_brain_status_to_private": "Sim, definir como privado",
|
||||
"cancel_set_brain_status_to_private": "Não, mantê-lo público",
|
||||
|
@ -6,7 +6,7 @@
|
||||
"success": "Arquivo enviado com sucesso",
|
||||
"uploadFailed": "Falha ao enviar arquivo: {{message}}",
|
||||
"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",
|
||||
"selectBrain": "Por favor, selecione ou crie um cérebro para enviar um arquivo",
|
||||
"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. 🧠💡🥲",
|
||||
"invalidFileType": "Tipo de arquivo inválido",
|
||||
"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"
|
||||
}
|
@ -30,7 +30,7 @@
|
||||
"private_brain_label": "Приватный",
|
||||
"public_brain_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/>У них не будет доступа к вашим загруженным файлам и разделу 'люди'.",
|
||||
"confirm_set_brain_status_to_public": "Да, установить как публичный",
|
||||
"cancel_set_brain_status_to_public": "Нет, оставить приватным",
|
||||
@ -40,7 +40,7 @@
|
||||
"public_brain_subscription_success_message": "Вы успешно подписались на мозг",
|
||||
"public_brain_last_update_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 больше не смогут использовать этот мозг, и они не увидят его в библиотеке мозгов.",
|
||||
"confirm_set_brain_status_to_private": "Да, установить как частное",
|
||||
"cancel_set_brain_status_to_private": "Нет, оставить общедоступным",
|
||||
|
@ -6,7 +6,7 @@
|
||||
"success": "Файл успешно загружен",
|
||||
"uploadFailed": "Не удалось загрузить файл: {{message}}",
|
||||
"maxSizeError": "Файл слишком большой",
|
||||
"alreadyAdded": "{{fileName}} уже добавлен",
|
||||
"alreadyAdded": "{{fileName}} уже добавлен, но не отправлен в ваш мозг.",
|
||||
"addFiles": "Пожалуйста, добавьте файлы для загрузки",
|
||||
"selectBrain": "Пожалуйста, выберите или создайте мозг для загрузки файла",
|
||||
"invalidUrl": "Неверный URL",
|
||||
@ -16,5 +16,5 @@
|
||||
"missingNecessaryRole": "У вас нет необходимой роли для загрузки контента в выбранный мозг. 🧠💡🥲",
|
||||
"invalidFileType": "Неверный тип файла",
|
||||
"feed_form_submit_button": "Отправить в мой мозг",
|
||||
"selected_brain_select_label": "Выбран мозг"
|
||||
"selected_brain_select_label": "Выберите мозг"
|
||||
}
|
||||
|
@ -30,7 +30,7 @@
|
||||
"private_brain_label": "私有",
|
||||
"public_brain_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/>他们将无法访问您上传的文件和人员部分。",
|
||||
"confirm_set_brain_status_to_public": "是的,设为公共",
|
||||
"cancel_set_brain_status_to_public": "不,保持私密",
|
||||
@ -40,7 +40,7 @@
|
||||
"public_brain_subscription_success_message": "Вы успешно подписались на мозг",
|
||||
"public_brain_last_update_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的用户将无法再使用此大脑,并且不会在大脑库中看到它。",
|
||||
"confirm_set_brain_status_to_private": "是的,设为私有",
|
||||
"cancel_set_brain_status_to_private": "不,保持为公开",
|
||||
|
@ -6,7 +6,7 @@
|
||||
"success": "文件上传成功",
|
||||
"uploadFailed": "上传文件失败:{{message}}",
|
||||
"maxSizeError": "文件太大",
|
||||
"alreadyAdded": "已添加 {{filename}}",
|
||||
"alreadyAdded": "{{fileName}} 已经添加,但尚未发送到您的大脑。",
|
||||
"addFiles": "请添加要上传的文件",
|
||||
"selectBrain": "请选择或创建一个大脑来上传文件。",
|
||||
"invalidUrl": "无效的URL",
|
||||
@ -16,5 +16,5 @@
|
||||
"missingNecessaryRole": "您没有所选大脑的上传权限。 🧠💡🥲",
|
||||
"invalidFileType": "文件类型不受支持",
|
||||
"feed_form_submit_button":"发送到我的大脑",
|
||||
"selected_brain_select_label": "已选择的大脑"
|
||||
"selected_brain_select_label": "选择一个大脑"
|
||||
}
|
@ -17,6 +17,9 @@ module.exports = {
|
||||
colors: {
|
||||
black: "#00121F",
|
||||
primary: "#4F46E5",
|
||||
"chat-bg-gray": "#D9D9D9",
|
||||
"msg-gray": "#9B9B9B",
|
||||
"msg-purple": "#E0DDFC",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user