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:
Mamadou DICKO 2023-09-20 16:24:56 +02:00 committed by GitHub
parent d8e188788f
commit 1593c3342c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 139 additions and 51 deletions

View File

@ -32,3 +32,4 @@ class MinimalBrainEntity(BaseModel):
id: UUID
name: str
rights: RoleEnum
status: str

View File

@ -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):

View File

@ -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

View File

@ -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.

View File

@ -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>
);

View File

@ -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)}
>

View File

@ -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>
)}

View File

@ -350,7 +350,7 @@ export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => {
return {
handleSubmit,
register,
brain,
model,
temperature,
maxTokens,

View File

@ -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,
};
};

View File

@ -9,4 +9,5 @@ export const mapBackendMinimalBrainToMinimalBrain = (
id: backendMinimalBrain.id,
name: backendMinimalBrain.name,
role: backendMinimalBrain.rights,
status: backendMinimalBrain.status,
});

View 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>
);
};

View File

@ -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");

View File

@ -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

View File

@ -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>