feat(apiBrain): add api brain secrets field in knowledge tab (#1669)

Issue: https://github.com/StanGirard/quivr/issues/1572

Demo: 


https://github.com/StanGirard/quivr/assets/63923024/192ea114-c1a2-4fb1-8226-2c77a41129a7
This commit is contained in:
Mamadou DICKO 2023-11-20 18:11:39 +01:00 committed by GitHub
parent 3da64c0b0e
commit d7d10e3674
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 73 additions and 30 deletions

View File

@ -6,11 +6,12 @@ import Button from "@/lib/components/ui/Button";
import {
BrainTabTrigger,
KnowledgeTab,
KnowledgeOrSecretsTab,
PeopleTab,
SettingsTab,
} from "./components";
import { DeleteOrUnsubscribeConfirmationModal } from "./components/Modals/DeleteOrUnsubscribeConfirmationModal";
import { useBrainFetcher } from "./hooks/useBrainFetcher";
import { useBrainManagementTabs } from "./hooks/useBrainManagementTabs";
export const BrainManagementTabs = (): JSX.Element => {
@ -18,6 +19,7 @@ export const BrainManagementTabs = (): JSX.Element => {
"translation",
"config",
"delete_or_unsubscribe_from_brain",
"external_api_definition",
]);
const {
selectedTab,
@ -31,6 +33,14 @@ export const BrainManagementTabs = (): JSX.Element => {
isOwnedByCurrentUser,
isDeleteOrUnsubscribeRequestPending,
} = useBrainManagementTabs();
const { brain } = useBrainFetcher({
brainId,
});
const knowledgeOrSecretsTabLabel =
brain?.brain_type === "doc"
? t("knowledge", { ns: "config" })
: t("secrets", { ns: "external_api_definition" });
if (brainId === undefined) {
return <div />;
@ -61,9 +71,9 @@ export const BrainManagementTabs = (): JSX.Element => {
onChange={setSelectedTab}
/>
<BrainTabTrigger
selected={selectedTab === "knowledge"}
label={t("knowledge", { ns: "config" })}
value="knowledge"
selected={selectedTab === "knowledgeOrSecrets"}
label={knowledgeOrSecretsTabLabel}
value="knowledgeOrSecrets"
onChange={setSelectedTab}
/>
</>
@ -77,8 +87,11 @@ export const BrainManagementTabs = (): JSX.Element => {
<Content value="people">
<PeopleTab brainId={brainId} hasEditRights={hasEditRights} />
</Content>
<Content value="knowledge">
<KnowledgeTab brainId={brainId} hasEditRights={hasEditRights} />
<Content value="knowledgeOrSecrets">
<KnowledgeOrSecretsTab
brainId={brainId}
hasEditRights={hasEditRights}
/>
</Content>
</div>

View File

@ -2,27 +2,35 @@
import { UUID } from "crypto";
import { useTranslation } from "react-i18next";
import { ApiBrainSecretsInputs } from "@/lib/components/ApiBrainSecretsInputs/ApiBrainSecretsInputs";
import { Divider } from "@/lib/components/ui/Divider";
import { KnowledgeToFeedProvider } from "@/lib/context";
import { AddKnowledge } from "./components/AddKnowledge/AddKnowledge";
import { AddedKnowledge } from "./components/AddedKnowledge/AddedKnowledge";
import { useBrainFetcher } from "../../hooks/useBrainFetcher";
import { NoAccess } from "../NoAccess";
type KnowledgeTabProps = {
type KnowledgeOrSecretsTabProps = {
brainId: UUID;
hasEditRights: boolean;
};
export const KnowledgeTab = ({
export const KnowledgeOrSecretsTab = ({
brainId,
hasEditRights,
}: KnowledgeTabProps): JSX.Element => {
}: KnowledgeOrSecretsTabProps): JSX.Element => {
const { t } = useTranslation(["translation", "explore", "config"]);
const { brain } = useBrainFetcher({
brainId,
});
if (!hasEditRights) {
return <NoAccess />;
}
if (brain?.brain_type === "api") {
return <ApiBrainSecretsInputs brainId={brainId} />;
}
return (
<KnowledgeToFeedProvider>
<main>

View File

@ -1,4 +1,4 @@
export * from "./BrainTabTrigger";
export * from "./KnowledgeTab";
export * from "./KnowledgeOrSecretsTab";
export * from "./PeopleTab";
export * from "./SettingsTab";

View File

@ -1,3 +1,7 @@
export const brainManagementTabs = ["settings", "people", "knowledge"] as const;
export const brainManagementTabs = [
"settings",
"people",
"knowledgeOrSecrets",
] as const;
export type BrainManagementTab = (typeof brainManagementTabs)[number];

View File

@ -19,7 +19,8 @@ export const ApiBrainSecretsInputs = ({
const { brain } = useBrainFetcher({
brainId,
});
const { register, updateSecrets, isPending } = useApiBrainSecretsInputs({
const { register, updateSecrets, isPending, isUpdateButtonDisabled } =
useApiBrainSecretsInputs({
brainId,
onUpdate,
});
@ -61,7 +62,9 @@ export const ApiBrainSecretsInputs = ({
))}
</div>
<div className="mt-4 flex justify-end">
<Button isLoading={isPending}>{t("update_secrets_button")}</Button>
<Button disabled={isUpdateButtonDisabled} isLoading={isPending}>
{t("update_secrets_button")}
</Button>
</div>
</div>
</form>

View File

@ -1,11 +1,14 @@
import { useMutation } from "@tanstack/react-query";
import { UUID } from "crypto";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useBrainApi } from "@/lib/api/brain/useBrainApi";
import { useToast } from "@/lib/hooks";
import { getNonEmptyValuesFromDict } from "../utils/getNonEmptyValuesFromDict";
type UseApiBrainSecretsInputsProps = {
brainId: UUID;
onUpdate?: () => void;
@ -17,23 +20,21 @@ export const useApiBrainSecretsInputs = ({
onUpdate,
}: UseApiBrainSecretsInputsProps) => {
const { t } = useTranslation(["brain"]);
const { register, getValues } = useForm<Record<string, string>>();
const { register, watch } = useForm<Record<string, string>>();
const { updateBrainSecrets } = useBrainApi();
const { publish } = useToast();
const updateSecretsHandler = async () => {
const values = getValues();
const nonEmptyValues = Object.entries(values).reduce(
(acc, [key, value]) => {
if (value !== "") {
acc[key] = value;
}
const values = watch();
return acc;
},
{} as Record<string, string>
);
await updateBrainSecrets(brainId, nonEmptyValues);
const [isUpdateButtonDisabled, setIsUpdateButtonDisabled] = useState(false);
useEffect(() => {
const nonEmptyValues = getNonEmptyValuesFromDict(values);
setIsUpdateButtonDisabled(Object.keys(nonEmptyValues).length === 0);
}, [values]);
const updateSecretsHandler = async () => {
await updateBrainSecrets(brainId, getNonEmptyValuesFromDict(values));
onUpdate?.();
};
@ -51,5 +52,6 @@ export const useApiBrainSecretsInputs = ({
register,
updateSecrets,
isPending,
isUpdateButtonDisabled,
};
};

View File

@ -0,0 +1,13 @@
export const getNonEmptyValuesFromDict = (
values: Record<string, string>
): Record<string, string> => {
const nonEmptyValues = Object.entries(values).reduce((acc, [key, value]) => {
if (value !== "") {
acc[key] = value;
}
return acc;
}, {} as Record<string, string>);
return nonEmptyValues;
};