mirror of
https://github.com/QuivrHQ/quivr.git
synced 2024-12-14 17:03:29 +03:00
feat: add new brain management page (#1906)
Issue: https://github.com/StanGirard/quivr/issues/1902 Demo: https://github.com/StanGirard/quivr/assets/63923024/33170c91-3bdc-442a-918a-5013b423f8e5
This commit is contained in:
parent
7db832d200
commit
7480e89fa9
@ -2,9 +2,10 @@ from enum import Enum
|
||||
from typing import List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from modules.brain.entity.api_brain_definition_entity import ApiBrainDefinitionEntity
|
||||
from pydantic import BaseModel
|
||||
|
||||
from modules.brain.entity.api_brain_definition_entity import ApiBrainDefinitionEntity
|
||||
|
||||
|
||||
class BrainType(str, Enum):
|
||||
DOC = "doc"
|
||||
@ -67,3 +68,4 @@ class MinimalUserBrainEntity(BaseModel):
|
||||
rights: RoleEnum
|
||||
status: str
|
||||
brain_type: BrainType
|
||||
description: str
|
||||
|
@ -2,6 +2,7 @@ from uuid import UUID
|
||||
|
||||
from logger import get_logger
|
||||
from models.settings import get_supabase_client
|
||||
|
||||
from modules.brain.entity.brain_entity import BrainUser, MinimalUserBrainEntity
|
||||
from modules.brain.repository.interfaces.brains_users_interface import (
|
||||
BrainsUsersInterface,
|
||||
@ -18,7 +19,9 @@ class BrainsUsers(BrainsUsersInterface):
|
||||
def get_user_brains(self, user_id) -> list[MinimalUserBrainEntity]:
|
||||
response = (
|
||||
self.db.from_("brains_users")
|
||||
.select("id:brain_id, rights, brains (brain_id, name, status, brain_type)")
|
||||
.select(
|
||||
"id:brain_id, rights, brains (brain_id, name, status, brain_type, description)"
|
||||
)
|
||||
.filter("user_id", "eq", user_id)
|
||||
.execute()
|
||||
)
|
||||
@ -31,6 +34,9 @@ class BrainsUsers(BrainsUsersInterface):
|
||||
rights=item["rights"],
|
||||
status=item["brains"]["status"],
|
||||
brain_type=item["brains"]["brain_type"],
|
||||
description=item["brains"]["description"]
|
||||
if item["brains"]["description"] is not None
|
||||
else "",
|
||||
)
|
||||
)
|
||||
user_brains[-1].rights = item["rights"]
|
||||
@ -40,7 +46,7 @@ class BrainsUsers(BrainsUsersInterface):
|
||||
response = (
|
||||
self.db.from_("brains_users")
|
||||
.select(
|
||||
"id:brain_id, rights, brains (id: brain_id, status, name, brain_type)"
|
||||
"id:brain_id, rights, brains (id: brain_id, status, name, brain_type, description)"
|
||||
)
|
||||
.filter("user_id", "eq", user_id)
|
||||
.filter("brain_id", "eq", brain_id)
|
||||
@ -56,6 +62,9 @@ class BrainsUsers(BrainsUsersInterface):
|
||||
rights=brain_data["rights"],
|
||||
status=brain_data["brains"]["status"],
|
||||
brain_type=brain_data["brains"]["brain_type"],
|
||||
description=brain_data["brains"]["description"]
|
||||
if brain_data["brains"]["description"] is not None
|
||||
else "",
|
||||
)
|
||||
|
||||
def delete_brain_user_by_id(
|
||||
|
@ -1,27 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { UUID } from "crypto";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BrainManagementTabs } from "./components";
|
||||
|
||||
const BrainsManagement = (): JSX.Element => {
|
||||
const params = useParams();
|
||||
const { t } = useTranslation(["brain"]);
|
||||
|
||||
const brainId = params?.brainId as UUID | undefined;
|
||||
|
||||
if (brainId === undefined) {
|
||||
return (
|
||||
<div className="flex justify-center mt-5 w-full">
|
||||
<div className="bg-blue-100 border border-blue-400 text-blue-700 px-4 py-3 rounded relative max-w-md h-fit">
|
||||
<p>{t("selectBrain")}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <BrainManagementTabs />;
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,60 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import Spinner from "@/lib/components/ui/Spinner";
|
||||
import { Tabs, TabsContent, TabsList } from "@/lib/components/ui/Tabs";
|
||||
|
||||
import { BrainSearchBar } from "./components/BrainSearchBar";
|
||||
import { BrainsList } from "./components/BrainsList";
|
||||
import { StyledTabsTrigger } from "./components/StyledTabsTrigger";
|
||||
import { useBrainsTabs } from "./hooks/useBrainsTabs";
|
||||
|
||||
export const BrainsTabs = (): JSX.Element => {
|
||||
const { t } = useTranslation(["brain", "translation"]);
|
||||
const {
|
||||
searchQuery,
|
||||
isFetchingBrains,
|
||||
setSearchQuery,
|
||||
brains,
|
||||
privateBrains,
|
||||
publicBrains,
|
||||
} = useBrainsTabs();
|
||||
|
||||
if (isFetchingBrains && brains.length === 0) {
|
||||
return (
|
||||
<div className="flex w-full h-full justify-center items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs defaultValue="all" className="flex flex-col">
|
||||
<TabsList className="flex flex-row justify-start gap-2 border-b-2 rounded-none pb-3">
|
||||
<StyledTabsTrigger value="all">
|
||||
{t("translation:all")}
|
||||
</StyledTabsTrigger>
|
||||
<StyledTabsTrigger value="private" className="capitalize">
|
||||
{t("private_brain_label")}
|
||||
</StyledTabsTrigger>
|
||||
<StyledTabsTrigger value="public" className="capitalize">
|
||||
{t("public_brain_label")}
|
||||
</StyledTabsTrigger>
|
||||
<div className="w-full flex justify-end">
|
||||
<BrainSearchBar
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
/>
|
||||
</div>
|
||||
</TabsList>
|
||||
<TabsContent value="all">
|
||||
<BrainsList brains={brains} />
|
||||
</TabsContent>
|
||||
<TabsContent value="private">
|
||||
<BrainsList brains={privateBrains} />
|
||||
</TabsContent>
|
||||
<TabsContent value="public">
|
||||
<BrainsList brains={publicBrains} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
);
|
||||
};
|
@ -0,0 +1,55 @@
|
||||
import Link from "next/link";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CgFileDocument } from "react-icons/cg";
|
||||
import { LuChevronRightCircle } from "react-icons/lu";
|
||||
|
||||
import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types";
|
||||
import { getBrainIconFromBrainType } from "@/lib/helpers/getBrainIconFromBrainType";
|
||||
|
||||
type BrainItemProps = {
|
||||
brain: MinimalBrainForUser;
|
||||
};
|
||||
|
||||
export const BrainItem = ({ brain }: BrainItemProps): JSX.Element => {
|
||||
const { t } = useTranslation("brain");
|
||||
|
||||
const isBrainDescriptionEmpty = brain.description === "";
|
||||
const brainDescription = isBrainDescriptionEmpty
|
||||
? t("empty_brain_description")
|
||||
: brain.description;
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center flex-col flex-1 w-full h-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 pb-2 bg-secondary">
|
||||
<div className="w-full">
|
||||
<div className="w-full py-2 flex gap-2 justify-center items-center bg-primary bg-opacity-40 px-2">
|
||||
{getBrainIconFromBrainType(brain.brain_type, {
|
||||
iconSize: 24,
|
||||
DocBrainIcon: CgFileDocument,
|
||||
iconClassName: "text-primary",
|
||||
})}
|
||||
<span className="line-clamp-1 mr-2 font-semibold text-md">
|
||||
{brain.name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 py-2">
|
||||
<p
|
||||
className={`line-clamp-2 text-center px-5 ${
|
||||
isBrainDescriptionEmpty && "text-gray-400"
|
||||
}`}
|
||||
>
|
||||
{brainDescription}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full px-2">
|
||||
<Link
|
||||
href={`/brains-management/${brain.id}`}
|
||||
className="px-8 py-3 flex items-center justify-center gap-2 bg-white text-primary rounded-lg border-0 w-content mt-3 disabled:bg-secondary hover:bg-primary/50 disabled:hover:bg-primary/50 w-full text-md"
|
||||
>
|
||||
<span>Modifier</span>
|
||||
<LuChevronRightCircle className="text-md" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,29 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { LuSearch } from "react-icons/lu";
|
||||
|
||||
import Field from "@/lib/components/ui/Field";
|
||||
|
||||
type BrainSearchBarProps = {
|
||||
searchQuery: string;
|
||||
setSearchQuery: (searchQuery: string) => void;
|
||||
};
|
||||
|
||||
export const BrainSearchBar = ({
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
}: BrainSearchBarProps): JSX.Element => {
|
||||
const { t } = useTranslation(["brain"]);
|
||||
|
||||
return (
|
||||
<Field
|
||||
name="brainsearch"
|
||||
placeholder={t("searchBrain")}
|
||||
autoComplete="off"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-auto"
|
||||
inputClassName="w-max w-[200px] rounded-3xl border-none"
|
||||
icon={<LuSearch className="text-primary" size={20} />}
|
||||
/>
|
||||
);
|
||||
};
|
@ -0,0 +1,21 @@
|
||||
import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types";
|
||||
|
||||
import { BrainItem } from "./BrainItem";
|
||||
|
||||
type BrainsListProps = {
|
||||
brains: MinimalBrainForUser[];
|
||||
};
|
||||
|
||||
export const BrainsList = ({ brains }: BrainsListProps): JSX.Element => {
|
||||
return (
|
||||
<div className="flex flex-1 flex-col items-center justify-center">
|
||||
<div className="w-full lg:grid-cols-4 md:grid-cols-3 grid mt-5 gap-3 items-stretch">
|
||||
{brains.map((brain) => (
|
||||
<div key={brain.id} className="h-[180px]">
|
||||
<BrainItem brain={brain} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,21 @@
|
||||
import { forwardRef } from "react";
|
||||
|
||||
import { TabsTrigger } from "@/lib/components/ui/Tabs";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export const StyledTabsTrigger = forwardRef<
|
||||
React.ElementRef<typeof TabsTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsTrigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"capitalize",
|
||||
"data-[state=active]:shadow-none",
|
||||
"data-[state=active]:text-primary",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
StyledTabsTrigger.displayName = TabsTrigger.displayName;
|
@ -0,0 +1,29 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useBrainsTabs = () => {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const { allBrains, isFetchingBrains } = useBrainContext();
|
||||
|
||||
const brains = allBrains.filter((brain) => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
const name = brain.name.toLowerCase();
|
||||
|
||||
return name.includes(query);
|
||||
});
|
||||
|
||||
const privateBrains = brains.filter((brain) => brain.status === "private");
|
||||
|
||||
const publicBrains = brains.filter((brain) => brain.status === "public");
|
||||
|
||||
return {
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
brains,
|
||||
privateBrains,
|
||||
publicBrains,
|
||||
isFetchingBrains,
|
||||
};
|
||||
};
|
@ -0,0 +1,2 @@
|
||||
const brainTabs = ["all", "private", "public"] as const;
|
||||
export type BrainTab = (typeof brainTabs)[number];
|
@ -4,8 +4,6 @@ import { ReactNode } from "react";
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
|
||||
import { BrainsList } from "./[brainId]/components";
|
||||
|
||||
interface LayoutProps {
|
||||
children?: ReactNode;
|
||||
}
|
||||
@ -18,7 +16,6 @@ const Layout = ({ children }: LayoutProps): JSX.Element => {
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full flex justify-stretch items-stretch overflow-scroll">
|
||||
<BrainsList />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
@ -1,4 +1,33 @@
|
||||
"use client";
|
||||
import BrainsManagementPage from "./[brainId]/page";
|
||||
|
||||
export default BrainsManagementPage;
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { LuBrain } from "react-icons/lu";
|
||||
|
||||
import { AddBrainModal } from "@/lib/components/AddBrainModal";
|
||||
|
||||
import { BrainsTabs } from "./components/BrainsTabs/BrainsTabs";
|
||||
|
||||
const BrainsManagement = (): JSX.Element => {
|
||||
const { t } = useTranslation("chat");
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="w-full h-full p-6 bg-highlight flex flex-col flex-1 overflow-auto">
|
||||
<div className="w-full mb-10">
|
||||
<div className="flex flex-row justify-center items-center gap-2">
|
||||
<LuBrain size={20} className="text-primary" />
|
||||
<span className="capitalize text-2xl font-semibold">
|
||||
{t("brains")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<BrainsTabs />
|
||||
</div>
|
||||
<div className="w-full flex justify-center py-4">
|
||||
<AddBrainModal triggerClassName="bg-primary text-white font-normal" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BrainsManagement;
|
||||
|
@ -11,4 +11,5 @@ export const mapBackendMinimalBrainToMinimalBrain = (
|
||||
role: backendMinimalBrain.rights,
|
||||
status: backendMinimalBrain.status,
|
||||
brain_type: backendMinimalBrain.brain_type,
|
||||
description: backendMinimalBrain.description,
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MdAdd } from "react-icons/md";
|
||||
import { LuPlusCircle } from "react-icons/lu";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import { Modal } from "@/lib/components/ui/Modal";
|
||||
@ -32,8 +32,8 @@ export const AddBrainSteps = ({
|
||||
className={cn("border-0", triggerClassName)}
|
||||
data-testid="add-brain-button"
|
||||
>
|
||||
<LuPlusCircle className="text-xl" />
|
||||
{t("newBrain", { ns: "brain" })}
|
||||
<MdAdd className="text-xl" />
|
||||
</Button>
|
||||
}
|
||||
title={t("newBrainTitle", { ns: "brain" })}
|
||||
|
@ -27,7 +27,7 @@ export const Menu = (): JSX.Element => {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const displayedOnPages = ["/chat", "/library"];
|
||||
const displayedOnPages = ["/chat", "/library", "/brains-management"];
|
||||
|
||||
const isMenuDisplayed = displayedOnPages.some((page) =>
|
||||
pathname.includes(page)
|
||||
|
55
frontend/lib/components/ui/Tabs.tsx
Normal file
55
frontend/lib/components/ui/Tabs.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Tabs = TabsPrimitive.Root;
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsList.displayName = TabsPrimitive.List.displayName;
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
||||
|
||||
export { Tabs, TabsContent, TabsList, TabsTrigger };
|
@ -25,7 +25,7 @@ export const useBrainProvider = () => {
|
||||
const [allBrains, setAllBrains] = useState<MinimalBrainForUser[]>([]);
|
||||
const [currentBrainId, setCurrentBrainId] = useState<null | UUID>(null);
|
||||
const [defaultBrainId, setDefaultBrainId] = useState<UUID>();
|
||||
const [isFetchingBrains, setIsFetchingBrains] = useState(false);
|
||||
const [isFetchingBrains, setIsFetchingBrains] = useState(true);
|
||||
const [publicPrompts, setPublicPrompts] = useState<Prompt[]>([]);
|
||||
const [currentPromptId, setCurrentPromptId] = useState<null | string>(null);
|
||||
|
||||
|
@ -29,6 +29,7 @@ export type MinimalBrainForUser = {
|
||||
role: BrainRoleType;
|
||||
status: BrainAccessStatus;
|
||||
brain_type: BrainType;
|
||||
description: string;
|
||||
};
|
||||
|
||||
//TODO: rename rights to role in Backend and use MinimalBrainForUser instead of BackendMinimalBrainForUser
|
||||
|
@ -52,7 +52,6 @@
|
||||
"searchBrain": "Search for a brain",
|
||||
"secrets_update_error": "Error while updating secrets",
|
||||
"secrets_updated": "Secrets updated",
|
||||
"selectBrain": "Select a brain please",
|
||||
"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_title": "Are you sure you want to set this as <span class='text-primary'>Private</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.",
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"all": "All",
|
||||
"and": "and",
|
||||
"cancel": "Cancel",
|
||||
"Chat": "Chat",
|
||||
|
@ -52,7 +52,6 @@
|
||||
"searchBrain": "Buscar un cerebro",
|
||||
"secrets_update_error": "Error al actualizar secretos",
|
||||
"secrets_updated": "Secretos actualizados",
|
||||
"selectBrain": "Por favor, selecciona un cerebro",
|
||||
"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_title": "¿Estás seguro de que quieres establecer esto como <span class='text-primary'>Privado</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.",
|
||||
|
@ -1,7 +1,11 @@
|
||||
{
|
||||
"all": "Todos",
|
||||
"and": "y",
|
||||
"cancel": "Cancelar",
|
||||
"Chat": "Conversar",
|
||||
"chatButton": "Conversar",
|
||||
"comingSoon": "Próximamente",
|
||||
"crawlButton": "Rastrear",
|
||||
"createButton": "Crear",
|
||||
"deleteButton": "Borrar",
|
||||
"deleteForeverButton": "Borrar para siempre",
|
||||
@ -10,28 +14,25 @@
|
||||
"Editor": "Editor",
|
||||
"email": "Correo electrónico",
|
||||
"Explore": "Explorar",
|
||||
"invalidUrl": "URL inválida",
|
||||
"lang": "es-ES",
|
||||
"languageSelect": "Idioma preferido",
|
||||
"loading": "Cargando...",
|
||||
"logoutButton": "Cerrar sesión",
|
||||
"newChatButton": "Nueva conversación",
|
||||
"next": "Siguiente",
|
||||
"or": "o",
|
||||
"and": "y",
|
||||
"Owner": "Propietario",
|
||||
"previous": "Anterior",
|
||||
"resetButton": "Restaurar",
|
||||
"saveButton": "Guardar",
|
||||
"shareButton": "Compartir",
|
||||
"themeSelect": "Tema de interfaz",
|
||||
"title": "Quivr - Tu segundo cerebro con IA generativa",
|
||||
"toastDismiss": "cerrar",
|
||||
"updateButton": "Actualizar",
|
||||
"Upload": "Subir",
|
||||
"uploadButton": "Subir",
|
||||
"uploadingButton": "Subiendo...",
|
||||
"Viewer": "Espectador",
|
||||
"saveButton": "Guardar",
|
||||
"invalidUrl": "URL inválida",
|
||||
"crawlButton": "Rastrear",
|
||||
"themeSelect": "Tema de interfaz",
|
||||
"languageSelect": "Idioma preferido",
|
||||
"cancel": "Cancelar",
|
||||
"next": "Siguiente",
|
||||
"previous": "Anterior"
|
||||
}
|
||||
"Viewer": "Espectador"
|
||||
}
|
@ -52,7 +52,6 @@
|
||||
"searchBrain": "Rechercher un cerveau",
|
||||
"secrets_update_error": "Erreur lors de la mise à jour des secrets",
|
||||
"secrets_updated": "Secrets mis à jour",
|
||||
"selectBrain": "Sélectionnez un cerveau, s'il vous plaît",
|
||||
"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_title": "Êtes-vous sûr de vouloir définir ceci comme <span class='text-primary'>Privé</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.",
|
||||
|
@ -1,37 +1,38 @@
|
||||
{
|
||||
"lang": "fr-FR",
|
||||
"title": "Quivr - Obtenez un deuxième cerveau avec l'IA générative",
|
||||
"description": "Quivr est votre deuxième cerveau dans le nuage, conçu pour stocker et récupérer facilement des informations non structurées.",
|
||||
"toastDismiss": "ignorer",
|
||||
"email": "Email",
|
||||
"or": "ou",
|
||||
"all": "Tout",
|
||||
"and": "et",
|
||||
"logoutButton": "Déconnexion",
|
||||
"updateButton": "Mettre à jour",
|
||||
"uploadButton": "Télécharger",
|
||||
"uploadingButton": "Téléchargement...",
|
||||
"cancel": "Annuler",
|
||||
"Chat": "Chat",
|
||||
"chatButton": "Chat",
|
||||
"comingSoon": "Bientôt disponible",
|
||||
"crawlButton": "Crawler",
|
||||
"createButton": "Créer",
|
||||
"deleteButton": "Supprimer",
|
||||
"deleteForeverButton": "Supprimer définitivement",
|
||||
"description": "Quivr est votre deuxième cerveau dans le nuage, conçu pour stocker et récupérer facilement des informations non structurées.",
|
||||
"doneButton": "Fait",
|
||||
"resetButton": "Réinitialiser",
|
||||
"newChatButton": "Nouveau chat",
|
||||
"createButton": "Créer",
|
||||
"shareButton": "Partager",
|
||||
"Upload": "Télécharger",
|
||||
"Chat": "Chat",
|
||||
"Explore": "Explorer",
|
||||
"loading": "Chargement...",
|
||||
"comingSoon": "Bientôt disponible",
|
||||
"Viewer": "Visualiseur",
|
||||
"Editor": "Éditeur",
|
||||
"Owner": "Propriétaire",
|
||||
"saveButton": "Sauvegarder",
|
||||
"email": "Email",
|
||||
"Explore": "Explorer",
|
||||
"invalidUrl": "URL invalide",
|
||||
"crawlButton": "Crawler",
|
||||
"themeSelect": "Thème de l'interface",
|
||||
"lang": "fr-FR",
|
||||
"languageSelect": "Langue préférée",
|
||||
"cancel": "Annuler",
|
||||
"loading": "Chargement...",
|
||||
"logoutButton": "Déconnexion",
|
||||
"newChatButton": "Nouveau chat",
|
||||
"next": "Suivant",
|
||||
"previous": "Précédent"
|
||||
}
|
||||
"or": "ou",
|
||||
"Owner": "Propriétaire",
|
||||
"previous": "Précédent",
|
||||
"resetButton": "Réinitialiser",
|
||||
"saveButton": "Sauvegarder",
|
||||
"shareButton": "Partager",
|
||||
"themeSelect": "Thème de l'interface",
|
||||
"title": "Quivr - Obtenez un deuxième cerveau avec l'IA générative",
|
||||
"toastDismiss": "ignorer",
|
||||
"updateButton": "Mettre à jour",
|
||||
"Upload": "Télécharger",
|
||||
"uploadButton": "Télécharger",
|
||||
"uploadingButton": "Téléchargement...",
|
||||
"Viewer": "Visualiseur"
|
||||
}
|
@ -52,7 +52,6 @@
|
||||
"searchBrain": "Pesquisar cérebros",
|
||||
"secrets_update_error": "Erro ao atualizar segredos",
|
||||
"secrets_updated": "Segredos atualizados",
|
||||
"selectBrain": "Selecione um cérebro, por favor",
|
||||
"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_title": "Tem a certeza de que deseja definir isto como <span class='text-primary'>Privado</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.",
|
||||
|
@ -1,37 +1,38 @@
|
||||
{
|
||||
"lang": "pt-BR",
|
||||
"title": "Quivr - Tenha um Segundo Cérebro com IA Generativa",
|
||||
"description": "Quivr é o seu segundo cérebro na nuvem, projetado para armazenar e recuperar facilmente informações não estruturadas.",
|
||||
"toastDismiss": "fechar",
|
||||
"email": "Email",
|
||||
"or": "ou",
|
||||
"all": "Todos",
|
||||
"and": "e",
|
||||
"logoutButton": "Sair",
|
||||
"updateButton": "Atualizar",
|
||||
"uploadButton": "Enviar",
|
||||
"uploadingButton": "Enviando...",
|
||||
"cancel": "Cancelar",
|
||||
"Chat": "Chat",
|
||||
"chatButton": "Chat",
|
||||
"comingSoon": "Em breve",
|
||||
"crawlButton": "Rastrear",
|
||||
"createButton": "Criar",
|
||||
"deleteButton": "Excluir",
|
||||
"deleteForeverButton": "Excluir permanentemente",
|
||||
"description": "Quivr é o seu segundo cérebro na nuvem, projetado para armazenar e recuperar facilmente informações não estruturadas.",
|
||||
"doneButton": "Concluído",
|
||||
"resetButton": "Redefinir",
|
||||
"newChatButton": "Novo Chat",
|
||||
"createButton": "Criar",
|
||||
"shareButton": "Compartilhar",
|
||||
"Upload": "Enviar",
|
||||
"Chat": "Chat",
|
||||
"Explore": "Explorar",
|
||||
"loading": "Carregando...",
|
||||
"comingSoon": "Em breve",
|
||||
"Viewer": "Visualizador",
|
||||
"Editor": "Editor",
|
||||
"Owner": "Proprietário",
|
||||
"saveButton": "Salvar",
|
||||
"email": "Email",
|
||||
"Explore": "Explorar",
|
||||
"invalidUrl": "URL inválida",
|
||||
"crawlButton": "Rastrear",
|
||||
"themeSelect": "Tema de interface",
|
||||
"lang": "pt-BR",
|
||||
"languageSelect": "Língua preferida",
|
||||
"cancel": "Cancelar",
|
||||
"loading": "Carregando...",
|
||||
"logoutButton": "Sair",
|
||||
"newChatButton": "Novo Chat",
|
||||
"next": "Próximo",
|
||||
"previous": "Anterior"
|
||||
}
|
||||
"or": "ou",
|
||||
"Owner": "Proprietário",
|
||||
"previous": "Anterior",
|
||||
"resetButton": "Redefinir",
|
||||
"saveButton": "Salvar",
|
||||
"shareButton": "Compartilhar",
|
||||
"themeSelect": "Tema de interface",
|
||||
"title": "Quivr - Tenha um Segundo Cérebro com IA Generativa",
|
||||
"toastDismiss": "fechar",
|
||||
"updateButton": "Atualizar",
|
||||
"Upload": "Enviar",
|
||||
"uploadButton": "Enviar",
|
||||
"uploadingButton": "Enviando...",
|
||||
"Viewer": "Visualizador"
|
||||
}
|
@ -52,7 +52,6 @@
|
||||
"searchBrain": "Поиск мозга",
|
||||
"secrets_update_error": "Ошибка при обновлении секретов",
|
||||
"secrets_updated": "Секреты обновлены",
|
||||
"selectBrain": "Выберите мозг, пожалуйста",
|
||||
"set_brain_status_to_private_modal_description": "Пользователи Quivr больше не смогут использовать этот мозг, и они не увидят его в библиотеке мозгов.",
|
||||
"set_brain_status_to_private_modal_title": "Вы уверены, что хотите установить это как <span class='text-primary'>Частное</span>?<br/><br/>",
|
||||
"set_brain_status_to_public_modal_description": "Каждый пользователь Quivr сможет:<br/>- Подписаться на ваш мозг в 'библиотеке мозгов'.<br/>- Использовать этот мозг и проверить настройки подсказок и модели.<br/><br/>У них не будет доступа к вашим загруженным файлам и разделу 'люди'.",
|
||||
|
@ -1,37 +1,38 @@
|
||||
{
|
||||
"lang": "ru-RU",
|
||||
"title": "Quivr - Второй мозг с генеративным ИИ",
|
||||
"description": "Quivr - это ваш второй мозг в облаке, предназначенный для легкого хранения и извлечения неструктурированной информации.",
|
||||
"toastDismiss": "закрыть",
|
||||
"email": "Email",
|
||||
"or": "или",
|
||||
"all": "Все",
|
||||
"and": "и",
|
||||
"logoutButton": "Выйти",
|
||||
"updateButton": "Обновить",
|
||||
"uploadButton": "Загрузить",
|
||||
"uploadingButton": "Загрузка...",
|
||||
"cancel": "Отмена",
|
||||
"Chat": "Чат",
|
||||
"chatButton": "Чат",
|
||||
"comingSoon": "Скоро",
|
||||
"crawlButton": "Поиск",
|
||||
"createButton": "Создать",
|
||||
"deleteButton": "Удалить",
|
||||
"deleteForeverButton": "Удалить навсегда",
|
||||
"description": "Quivr - это ваш второй мозг в облаке, предназначенный для легкого хранения и извлечения неструктурированной информации.",
|
||||
"doneButton": "Готово",
|
||||
"resetButton": "Сбросить",
|
||||
"newChatButton": "Новый чат",
|
||||
"createButton": "Создать",
|
||||
"shareButton": "Поделиться",
|
||||
"Upload": "Загрузка",
|
||||
"Chat": "Чат",
|
||||
"Explore": "Исследовать",
|
||||
"loading": "Загрузка...",
|
||||
"comingSoon": "Скоро",
|
||||
"Viewer": "Просмотр",
|
||||
"Editor": "Редактор",
|
||||
"Owner": "Владелец",
|
||||
"saveButton": "Сохранить",
|
||||
"email": "Email",
|
||||
"Explore": "Исследовать",
|
||||
"invalidUrl": "Неверный URL",
|
||||
"crawlButton": "Поиск",
|
||||
"themeSelect": "Тема интерфейса",
|
||||
"lang": "ru-RU",
|
||||
"languageSelect": "предпочтительный язык",
|
||||
"cancel": "Отмена",
|
||||
"loading": "Загрузка...",
|
||||
"logoutButton": "Выйти",
|
||||
"newChatButton": "Новый чат",
|
||||
"next": "Далее",
|
||||
"previous": "Назад"
|
||||
}
|
||||
"or": "или",
|
||||
"Owner": "Владелец",
|
||||
"previous": "Назад",
|
||||
"resetButton": "Сбросить",
|
||||
"saveButton": "Сохранить",
|
||||
"shareButton": "Поделиться",
|
||||
"themeSelect": "Тема интерфейса",
|
||||
"title": "Quivr - Второй мозг с генеративным ИИ",
|
||||
"toastDismiss": "закрыть",
|
||||
"updateButton": "Обновить",
|
||||
"Upload": "Загрузка",
|
||||
"uploadButton": "Загрузить",
|
||||
"uploadingButton": "Загрузка...",
|
||||
"Viewer": "Просмотр"
|
||||
}
|
@ -52,7 +52,6 @@
|
||||
"searchBrain": "搜索大脑",
|
||||
"secrets_update_error": "更新秘密时出错",
|
||||
"secrets_updated": "秘密已更新",
|
||||
"selectBrain": "请选择一个大脑",
|
||||
"set_brain_status_to_private_modal_description": "Quivr 用户将无法再使用此大脑,并且不会在大脑库中看到它。",
|
||||
"set_brain_status_to_private_modal_title": "您确定要将此大脑设置为<span class='text-primary'>私有</span>模式吗?<br/><br/>",
|
||||
"set_brain_status_to_public_modal_description": "每个 Quivr 用户都可以:<br/>- 在 “大脑库” 中订阅此大脑。<br/>- 使用此大脑并检查提示和模型配置。<br/><br/>他们将无法访问您上传的文件和人员部分。",
|
||||
|
@ -1,37 +1,38 @@
|
||||
{
|
||||
"lang": "zh-CN",
|
||||
"title": "Quivr - 通过生成式人工智能获得第二个大脑",
|
||||
"description": "Quivr 是您在云中的第二个大脑,让您轻松存储和检索非结构化信息。",
|
||||
"toastDismiss": "好的",
|
||||
"email": "电子邮件",
|
||||
"or": "或",
|
||||
"all": "全部",
|
||||
"and": "和",
|
||||
"logoutButton": "登出",
|
||||
"updateButton": "更新",
|
||||
"uploadButton": "上传",
|
||||
"uploadingButton": "上传中…",
|
||||
"cancel": "取消",
|
||||
"Chat": "聊天",
|
||||
"chatButton": "聊天",
|
||||
"comingSoon": "即将推出",
|
||||
"crawlButton": "爬取",
|
||||
"createButton": "创建",
|
||||
"deleteButton": "删除",
|
||||
"deleteForeverButton": "永久删除",
|
||||
"description": "Quivr 是您在云中的第二个大脑,让您轻松存储和检索非结构化信息。",
|
||||
"doneButton": "完成",
|
||||
"resetButton": "重置",
|
||||
"newChatButton": "新聊天",
|
||||
"createButton": "创建",
|
||||
"shareButton": "分享",
|
||||
"Upload": "上传",
|
||||
"Chat": "聊天",
|
||||
"Explore": "探索",
|
||||
"loading": "加载中…",
|
||||
"comingSoon": "即将推出",
|
||||
"Viewer": "查看",
|
||||
"Editor": "编辑",
|
||||
"Owner": "作者",
|
||||
"saveButton": "保存",
|
||||
"email": "电子邮件",
|
||||
"Explore": "探索",
|
||||
"invalidUrl": "无效的 URL",
|
||||
"crawlButton": "爬取",
|
||||
"themeSelect": "主题",
|
||||
"lang": "zh-CN",
|
||||
"languageSelect": "首选语言",
|
||||
"cancel": "取消",
|
||||
"loading": "加载中…",
|
||||
"logoutButton": "登出",
|
||||
"newChatButton": "新聊天",
|
||||
"next": "下一个",
|
||||
"previous": "上一个"
|
||||
"or": "或",
|
||||
"Owner": "作者",
|
||||
"previous": "上一个",
|
||||
"resetButton": "重置",
|
||||
"saveButton": "保存",
|
||||
"shareButton": "分享",
|
||||
"themeSelect": "主题",
|
||||
"title": "Quivr - 通过生成式人工智能获得第二个大脑",
|
||||
"toastDismiss": "好的",
|
||||
"updateButton": "更新",
|
||||
"Upload": "上传",
|
||||
"uploadButton": "上传",
|
||||
"uploadingButton": "上传中…",
|
||||
"Viewer": "查看"
|
||||
}
|
Loading…
Reference in New Issue
Block a user