mirror of
https://github.com/StanGirard/quivr.git
synced 2024-12-24 03:41:56 +03:00
feat: display brain status on settings page (#1221)
* feat: update GET/brains return status * feat: add public tag on brain list * feat: add public tag for public brain on brain settings tab * feat: hide over tab when brain access is public
This commit is contained in:
parent
d8e188788f
commit
1593c3342c
@ -32,3 +32,4 @@ class MinimalBrainEntity(BaseModel):
|
||||
id: UUID
|
||||
name: str
|
||||
rights: RoleEnum
|
||||
status: str
|
||||
|
@ -58,7 +58,7 @@ class Brain(Repository):
|
||||
def get_user_brains(self, user_id) -> list[MinimalBrainEntity]:
|
||||
response = (
|
||||
self.db.from_("brains_users")
|
||||
.select("id:brain_id, rights, brains (id: brain_id, name)")
|
||||
.select("id:brain_id, rights, brains (id: brain_id, name, status)")
|
||||
.filter("user_id", "eq", user_id)
|
||||
.execute()
|
||||
)
|
||||
@ -69,6 +69,7 @@ class Brain(Repository):
|
||||
id=item["brains"]["id"],
|
||||
name=item["brains"]["name"],
|
||||
rights=item["rights"],
|
||||
status=item["brains"]["status"],
|
||||
)
|
||||
)
|
||||
user_brains[-1].rights = item["rights"]
|
||||
@ -77,7 +78,7 @@ class Brain(Repository):
|
||||
def get_brain_for_user(self, user_id, brain_id) -> MinimalBrainEntity | None:
|
||||
response = (
|
||||
self.db.from_("brains_users")
|
||||
.select("id:brain_id, rights, brains (id: brain_id, name)")
|
||||
.select("id:brain_id, rights, brains (id: brain_id, status, name)")
|
||||
.filter("user_id", "eq", user_id)
|
||||
.filter("brain_id", "eq", brain_id)
|
||||
.execute()
|
||||
@ -90,6 +91,7 @@ class Brain(Repository):
|
||||
id=brain_data["brains"]["id"],
|
||||
name=brain_data["brains"]["name"],
|
||||
rights=brain_data["rights"],
|
||||
status=brain_data["brains"]["status"],
|
||||
)
|
||||
|
||||
def get_brain_details(self, brain_id):
|
||||
|
@ -1,9 +1,10 @@
|
||||
from uuid import UUID
|
||||
|
||||
from models import BrainEntity, get_supabase_db
|
||||
from models import get_supabase_db
|
||||
from models.brain_entity import MinimalBrainEntity
|
||||
|
||||
|
||||
def get_user_brains(user_id: UUID) -> list[BrainEntity]:
|
||||
def get_user_brains(user_id: UUID) -> list[MinimalBrainEntity]:
|
||||
supabase_db = get_supabase_db()
|
||||
results = supabase_db.get_user_brains(user_id) # type: ignore
|
||||
|
||||
|
@ -21,6 +21,7 @@ from repository.brain import (
|
||||
update_brain_by_id,
|
||||
)
|
||||
from repository.prompt import delete_prompt_by_id, get_prompt_by_id
|
||||
|
||||
from routes.authorizations.brain_authorization import has_brain_authorization
|
||||
from routes.authorizations.types import RoleEnum
|
||||
|
||||
@ -31,7 +32,9 @@ brain_router = APIRouter()
|
||||
|
||||
# get all brains
|
||||
@brain_router.get("/brains/", dependencies=[Depends(AuthBearer())], tags=["Brain"])
|
||||
async def brain_endpoint(current_user: UserIdentity = Depends(get_current_user)):
|
||||
async def brain_endpoint(
|
||||
current_user: UserIdentity = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Retrieve all brains for the current user.
|
||||
|
||||
|
@ -1,17 +1,19 @@
|
||||
import Link from "next/link";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaBrain } from "react-icons/fa";
|
||||
|
||||
import { Chip } from "@/lib/components/ui/Chip";
|
||||
import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { useBrainListItem } from "./hooks/useBrainListItem";
|
||||
|
||||
interface BrainsListItemProps {
|
||||
type BrainsListItemProps = {
|
||||
brain: MinimalBrainForUser;
|
||||
}
|
||||
|
||||
};
|
||||
export const BrainListItem = ({ brain }: BrainsListItemProps): JSX.Element => {
|
||||
const { selected } = useBrainListItem(brain);
|
||||
const { t } = useTranslation("brain");
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -29,15 +31,20 @@ export const BrainListItem = ({ brain }: BrainsListItemProps): JSX.Element => {
|
||||
href={`/brains-management/${brain.id}`}
|
||||
key={brain.id}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<FaBrain className="text-xl" />
|
||||
<p>{brain.name}</p>
|
||||
<div className="flex flex-row flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<FaBrain className="text-xl" />
|
||||
<p>{brain.name}</p>
|
||||
</div>
|
||||
{brain.status === "public" && (
|
||||
<Chip className="ml-3">{t("public_brain_label")}</Chip>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div
|
||||
aria-hidden
|
||||
className="not-sr-only absolute left-1/2 top-0 bottom-0 right-0 bg-gradient-to-r from-transparent to-white dark:to-black pointer-events-none"
|
||||
className="not-sr-only absolute left-1/2 top-0 bottom-0 right-0 bg-gradient-to-r dark:to-black pointer-events-none"
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
|
@ -17,8 +17,11 @@ export const BrainManagementTabs = (): JSX.Element => {
|
||||
handleDeleteBrain,
|
||||
isDeleteModalOpen,
|
||||
setIsDeleteModalOpen,
|
||||
brain,
|
||||
} = useBrainManagementTabs();
|
||||
|
||||
const isPubliclyAccessible = brain?.status === "public";
|
||||
|
||||
if (brainId === undefined) {
|
||||
return <div />;
|
||||
}
|
||||
@ -38,21 +41,25 @@ export const BrainManagementTabs = (): JSX.Element => {
|
||||
value="settings"
|
||||
onChange={setSelectedTab}
|
||||
/>
|
||||
<BrainTabTrigger
|
||||
selected={selectedTab === "people"}
|
||||
label={t("people", { ns: "config" })}
|
||||
value="people"
|
||||
onChange={setSelectedTab}
|
||||
/>
|
||||
<BrainTabTrigger
|
||||
selected={selectedTab === "knowledge"}
|
||||
label={t("knowledge", { ns: "config" })}
|
||||
value="knowledge"
|
||||
onChange={setSelectedTab}
|
||||
/>
|
||||
{!isPubliclyAccessible && (
|
||||
<>
|
||||
<BrainTabTrigger
|
||||
selected={selectedTab === "people"}
|
||||
label={t("people", { ns: "config" })}
|
||||
value="people"
|
||||
onChange={setSelectedTab}
|
||||
/>
|
||||
<BrainTabTrigger
|
||||
selected={selectedTab === "knowledge"}
|
||||
label={t("knowledge", { ns: "config" })}
|
||||
value="knowledge"
|
||||
onChange={setSelectedTab}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</List>
|
||||
|
||||
<div className="flex-1 p-4 md:p-20">
|
||||
<div className="flex-1 p-4 md:p-20 md:pt-0">
|
||||
<Content value="settings">
|
||||
<SettingsTab brainId={brainId} />
|
||||
</Content>
|
||||
@ -66,6 +73,7 @@ export const BrainManagementTabs = (): JSX.Element => {
|
||||
|
||||
<div className="flex justify-center mt-4">
|
||||
<Button
|
||||
disabled={isPubliclyAccessible}
|
||||
className="px-8 md:px-20 py-2 bg-red-500 text-white rounded-md"
|
||||
onClick={() => setIsDeleteModalOpen(true)}
|
||||
>
|
||||
|
@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { FaSpinner } from "react-icons/fa";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import { Chip } from "@/lib/components/ui/Chip";
|
||||
import { Divider } from "@/lib/components/ui/Divider";
|
||||
import Field from "@/lib/components/ui/Field";
|
||||
import { TextArea } from "@/lib/components/ui/TextArea";
|
||||
@ -23,7 +24,6 @@ export const SettingsTab = ({ brainId }: SettingsTabProps): JSX.Element => {
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
|
||||
temperature,
|
||||
maxTokens,
|
||||
model,
|
||||
@ -36,8 +36,11 @@ export const SettingsTab = ({ brainId }: SettingsTabProps): JSX.Element => {
|
||||
pickPublicPrompt,
|
||||
removeBrainPrompt,
|
||||
accessibleModels,
|
||||
brain,
|
||||
} = useSettingsTab({ brainId });
|
||||
|
||||
const isPubliclyAccessible = brain?.status === "public";
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
@ -47,7 +50,7 @@ export const SettingsTab = ({ brainId }: SettingsTabProps): JSX.Element => {
|
||||
className="my-10 mb-0 flex flex-col items-center gap-2"
|
||||
ref={formRef}
|
||||
>
|
||||
<div className="flex flex-row flex-1 justify-between w-full">
|
||||
<div className="flex flex-row flex-1 justify-between w-full items-end">
|
||||
<div>
|
||||
<Field
|
||||
label={t("brainName", { ns: "brain" })}
|
||||
@ -55,24 +58,34 @@ export const SettingsTab = ({ brainId }: SettingsTabProps): JSX.Element => {
|
||||
autoComplete="off"
|
||||
className="flex-1"
|
||||
required
|
||||
disabled={isPubliclyAccessible}
|
||||
{...register("name")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
{isDefaultBrain ? (
|
||||
<div className="border rounded-lg border-dashed border-black dark:border-white bg-white dark:bg-black text-black dark:text-white focus:bg-black dark:focus:bg-white dark dark focus:text-white dark:focus:text-black transition-colors py-2 px-4 shadow-none">
|
||||
{t("defaultBrain", { ns: "brain" })}
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
variant={"secondary"}
|
||||
isLoading={isSettingAsDefault}
|
||||
onClick={() => void setAsDefaultBrainHandler()}
|
||||
type="button"
|
||||
>
|
||||
{t("setDefaultBrain", { ns: "brain" })}
|
||||
</Button>
|
||||
)}
|
||||
<div className="flex flex-1 items-center flex-col">
|
||||
{isPubliclyAccessible && (
|
||||
<Chip className="mb-3 bg-purple-600 text-white w-full">
|
||||
{t("brain:public_brain_label")}
|
||||
</Chip>
|
||||
)}
|
||||
{isDefaultBrain ? (
|
||||
<div className="border rounded-lg border-dashed border-black dark:border-white bg-white dark:bg-black text-black dark:text-white focus:bg-black dark:focus:bg-white dark dark focus:text-white dark:focus:text-black transition-colors py-2 px-4 shadow-none">
|
||||
{t("defaultBrain", { ns: "brain" })}
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
variant={"secondary"}
|
||||
isLoading={isSettingAsDefault}
|
||||
onClick={() => void setAsDefaultBrainHandler()}
|
||||
type="button"
|
||||
disabled={isPubliclyAccessible}
|
||||
>
|
||||
{t("setDefaultBrain", { ns: "brain" })}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TextArea
|
||||
@ -80,6 +93,7 @@ export const SettingsTab = ({ brainId }: SettingsTabProps): JSX.Element => {
|
||||
placeholder={t("brainDescriptionPlaceholder", { ns: "brain" })}
|
||||
autoComplete="off"
|
||||
className="flex-1 m-3"
|
||||
disabled={isPubliclyAccessible}
|
||||
{...register("description")}
|
||||
/>
|
||||
<Divider text={t("modelSection", { ns: "config" })} />
|
||||
@ -88,6 +102,7 @@ export const SettingsTab = ({ brainId }: SettingsTabProps): JSX.Element => {
|
||||
placeholder={t("openAiKeyPlaceholder", { ns: "config" })}
|
||||
autoComplete="off"
|
||||
className="flex-1"
|
||||
disabled={isPubliclyAccessible}
|
||||
{...register("openAiKey")}
|
||||
/>
|
||||
<fieldset className="w-full flex flex-col mt-2">
|
||||
@ -96,6 +111,7 @@ export const SettingsTab = ({ brainId }: SettingsTabProps): JSX.Element => {
|
||||
</label>
|
||||
<select
|
||||
id="model"
|
||||
disabled={isPubliclyAccessible}
|
||||
{...register("model")}
|
||||
className="px-5 py-2 dark:bg-gray-700 bg-gray-200 rounded-md"
|
||||
onChange={() => {
|
||||
@ -120,6 +136,7 @@ export const SettingsTab = ({ brainId }: SettingsTabProps): JSX.Element => {
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={temperature}
|
||||
disabled={isPubliclyAccessible}
|
||||
{...register("temperature")}
|
||||
/>
|
||||
</fieldset>
|
||||
@ -132,19 +149,24 @@ export const SettingsTab = ({ brainId }: SettingsTabProps): JSX.Element => {
|
||||
min="10"
|
||||
max={defineMaxTokens(model)}
|
||||
value={maxTokens}
|
||||
disabled={isPubliclyAccessible}
|
||||
{...register("maxTokens")}
|
||||
/>
|
||||
</fieldset>
|
||||
<div className="flex w-full justify-end py-4">
|
||||
<SaveButton handleSubmit={handleSubmit} />
|
||||
<SaveButton
|
||||
disabled={isPubliclyAccessible}
|
||||
handleSubmit={handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
<Divider text={t("customPromptSection", { ns: "config" })} />
|
||||
<PublicPrompts onSelect={pickPublicPrompt} />
|
||||
{!isPubliclyAccessible && <PublicPrompts onSelect={pickPublicPrompt} />}
|
||||
<Field
|
||||
label={t("promptName", { ns: "config" })}
|
||||
placeholder={t("promptNamePlaceholder", { ns: "config" })}
|
||||
autoComplete="off"
|
||||
className="flex-1"
|
||||
disabled={isPubliclyAccessible}
|
||||
{...register("prompt.title")}
|
||||
/>
|
||||
<TextArea
|
||||
@ -152,13 +174,20 @@ export const SettingsTab = ({ brainId }: SettingsTabProps): JSX.Element => {
|
||||
placeholder={t("promptContentPlaceholder", { ns: "config" })}
|
||||
autoComplete="off"
|
||||
className="flex-1"
|
||||
disabled={isPubliclyAccessible}
|
||||
{...register("prompt.content")}
|
||||
/>
|
||||
<div className="flex w-full justify-end py-4">
|
||||
<SaveButton handleSubmit={handleSubmit} />
|
||||
<SaveButton
|
||||
disabled={isPubliclyAccessible}
|
||||
handleSubmit={handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
{promptId !== "" && (
|
||||
<Button disabled={isUpdating} onClick={() => void removeBrainPrompt()}>
|
||||
<Button
|
||||
disabled={isUpdating || isPubliclyAccessible}
|
||||
onClick={() => void removeBrainPrompt()}
|
||||
>
|
||||
{t("removePrompt", { ns: "config" })}
|
||||
</Button>
|
||||
)}
|
||||
|
@ -350,7 +350,7 @@ export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => {
|
||||
return {
|
||||
handleSubmit,
|
||||
register,
|
||||
|
||||
brain,
|
||||
model,
|
||||
temperature,
|
||||
maxTokens,
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { UUID } from "crypto";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { getBrainDataKey } from "@/lib/api/brain/config";
|
||||
import { useBrainApi } from "@/lib/api/brain/useBrainApi";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
|
||||
import { BrainManagementTab } from "../types";
|
||||
@ -18,6 +21,7 @@ export const useBrainManagementTabs = () => {
|
||||
setSelectedTab(targetedTab);
|
||||
}
|
||||
}, []);
|
||||
const { getBrain } = useBrainApi();
|
||||
|
||||
const { deleteBrain, setCurrentBrainId } = useBrainContext();
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
@ -27,6 +31,12 @@ export const useBrainManagementTabs = () => {
|
||||
|
||||
const brainId = params?.brainId as UUID | undefined;
|
||||
|
||||
const { data: brain } = useQuery({
|
||||
queryKey: [getBrainDataKey(brainId!)],
|
||||
queryFn: () => getBrain(brainId!),
|
||||
enabled: brainId !== undefined,
|
||||
});
|
||||
|
||||
const handleDeleteBrain = () => {
|
||||
if (brainId === undefined) {
|
||||
return;
|
||||
@ -44,5 +54,6 @@ export const useBrainManagementTabs = () => {
|
||||
handleDeleteBrain,
|
||||
isDeleteModalOpen,
|
||||
setIsDeleteModalOpen,
|
||||
brain,
|
||||
};
|
||||
};
|
||||
|
@ -9,4 +9,5 @@ export const mapBackendMinimalBrainToMinimalBrain = (
|
||||
id: backendMinimalBrain.id,
|
||||
name: backendMinimalBrain.name,
|
||||
role: backendMinimalBrain.rights,
|
||||
status: backendMinimalBrain.status,
|
||||
});
|
||||
|
22
frontend/lib/components/ui/Chip.tsx
Normal file
22
frontend/lib/components/ui/Chip.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { HTMLProps } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export const Chip = ({
|
||||
label,
|
||||
children,
|
||||
className,
|
||||
...restProps
|
||||
}: HTMLProps<HTMLSpanElement>): JSX.Element => {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"px-2 bg-gray-400 text-black rounded-xl text-sm flex items-center justify-center",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{label ?? children}
|
||||
</span>
|
||||
);
|
||||
};
|
@ -84,10 +84,9 @@ import updatePassword_zh_cn from "../../../public/locales/zh-cn/updatePassword.j
|
||||
import upload_zh_cn from "../../../public/locales/zh-cn/upload.json";
|
||||
import user_zh_cn from "../../../public/locales/zh-cn/user.json";
|
||||
|
||||
type BrainTranslations = typeof import("../../../public/locales/en/brain.json");
|
||||
//type all translations
|
||||
export type Translations = {
|
||||
brain: BrainTranslations;
|
||||
brain: typeof import("../../../public/locales/en/brain.json");
|
||||
chat: typeof import("../../../public/locales/en/chat.json");
|
||||
config: typeof import("../../../public/locales/en/config.json");
|
||||
delete_brain: typeof import("../../../public/locales/en/deleteBrain.json");
|
||||
|
@ -6,11 +6,13 @@ import { Document } from "@/lib/types/Document";
|
||||
import { useBrainProvider } from "./hooks/useBrainProvider";
|
||||
import { Model } from "../../types/brainConfig";
|
||||
|
||||
export type BrainAccessStatus = "private" | "public";
|
||||
|
||||
export type Brain = {
|
||||
id: UUID;
|
||||
name: string;
|
||||
documents?: Document[];
|
||||
status?: string;
|
||||
status?: BrainAccessStatus;
|
||||
model?: Model;
|
||||
max_tokens?: number;
|
||||
temperature?: number;
|
||||
@ -23,6 +25,7 @@ export type MinimalBrainForUser = {
|
||||
id: UUID;
|
||||
name: string;
|
||||
role: BrainRoleType;
|
||||
status: BrainAccessStatus;
|
||||
};
|
||||
|
||||
//TODO: rename rights to role in Backend and use MinimalBrainForUser instead of BackendMinimalBrainForUser
|
||||
|
@ -3,10 +3,10 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
|
||||
interface Props {
|
||||
interface Props extends React.ComponentProps<typeof Button> {
|
||||
handleSubmit: (checkDirty: boolean) => Promise<void>;
|
||||
}
|
||||
export const SaveButton = ({ handleSubmit }: Props): ReactElement => {
|
||||
export const SaveButton = ({ handleSubmit, ...props }: Props): ReactElement => {
|
||||
const { t } = useTranslation(["translation"]);
|
||||
|
||||
return (
|
||||
@ -14,6 +14,7 @@ export const SaveButton = ({ handleSubmit }: Props): ReactElement => {
|
||||
variant={"primary"}
|
||||
onClick={() => void handleSubmit(true)}
|
||||
type="button"
|
||||
{...props}
|
||||
>
|
||||
{t("saveButton")}
|
||||
</Button>
|
||||
|
Loading…
Reference in New Issue
Block a user