mirror of
https://github.com/QuivrHQ/quivr.git
synced 2024-09-11 14:36:35 +03:00
feat(frontend): onboarding V2 (#2394)
# Description Please include a summary of the changes and the related issue. Please also include relevant motivation and context. ## Checklist before requesting a review Please delete options that are not relevant. - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented hard-to-understand areas - [ ] I have ideally added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged ## Screenshots (if appropriate): --------- Co-authored-by: Stan Girard <girard.stanislas@gmail.com>
This commit is contained in:
parent
6acf5fca5f
commit
7ff9abee1a
@ -58,16 +58,6 @@ async def retrieve_public_brains() -> list[PublicBrain]:
|
||||
return brain_service.get_public_brains()
|
||||
|
||||
|
||||
@brain_router.get(
|
||||
"/brains/default/", dependencies=[Depends(AuthBearer())], tags=["Brain"]
|
||||
)
|
||||
async def retrieve_default_brain(
|
||||
current_user: UserIdentity = Depends(get_current_user),
|
||||
):
|
||||
"""Retrieve or create the default brain for the current user."""
|
||||
brain = brain_user_service.get_default_user_brain_or_create_new(current_user)
|
||||
return {"id": brain.brain_id, "name": brain.name, "rights": "Owner"}
|
||||
|
||||
|
||||
@brain_router.get(
|
||||
"/brains/{brain_id}/",
|
||||
@ -220,19 +210,6 @@ async def update_existing_brain_secrets(
|
||||
return {"message": f"Brain {brain_id} has been updated."}
|
||||
|
||||
|
||||
@brain_router.post(
|
||||
"/brains/{brain_id}/default",
|
||||
dependencies=[Depends(AuthBearer()), Depends(has_brain_authorization())],
|
||||
tags=["Brain"],
|
||||
)
|
||||
async def set_brain_as_default(
|
||||
brain_id: UUID, user: UserIdentity = Depends(get_current_user)
|
||||
):
|
||||
"""Set a brain as the default for the current user."""
|
||||
brain_user_service.set_as_default_brain_for_user(user.id, brain_id)
|
||||
return {"message": f"Brain {brain_id} has been set as default brain."}
|
||||
|
||||
|
||||
@brain_router.post(
|
||||
"/brains/{brain_id}/documents",
|
||||
dependencies=[Depends(AuthBearer()), Depends(has_brain_authorization())],
|
||||
|
@ -32,6 +32,7 @@ class IntegrationDescriptionEntity(BaseModel):
|
||||
max_files: int
|
||||
allow_model_change: bool
|
||||
integration_display_name: str
|
||||
onboarding_brain: bool
|
||||
|
||||
|
||||
class IntegrationEntity(BaseModel):
|
||||
|
@ -22,7 +22,6 @@ from modules.brain.repository.interfaces.external_api_secrets_interface import (
|
||||
)
|
||||
from modules.brain.service.api_brain_definition_service import ApiBrainDefinitionService
|
||||
from modules.brain.service.brain_service import BrainService
|
||||
from modules.user.entity.user_identity import UserIdentity
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@ -74,33 +73,6 @@ class BrainUserService:
|
||||
brain_id=brain_id,
|
||||
)
|
||||
|
||||
def get_default_user_brain_or_create_new(self, user: UserIdentity):
|
||||
default_brain = self.get_user_default_brain(user.id)
|
||||
|
||||
if not default_brain:
|
||||
default_brain = brain_service.create_brain(brain=None, user_id=user.id)
|
||||
self.brain_user_repository.create_brain_user(
|
||||
user.id, default_brain.brain_id, RoleEnum.Owner, True
|
||||
)
|
||||
|
||||
return default_brain
|
||||
|
||||
def set_as_default_brain_for_user(self, user_id: UUID, brain_id: UUID):
|
||||
old_default_brain = self.get_user_default_brain(user_id)
|
||||
|
||||
if old_default_brain is not None:
|
||||
self.brain_user_repository.update_brain_user_default_status(
|
||||
user_id=user_id,
|
||||
brain_id=old_default_brain.brain_id,
|
||||
default_brain=False,
|
||||
)
|
||||
|
||||
self.brain_user_repository.update_brain_user_default_status(
|
||||
user_id=user_id,
|
||||
brain_id=brain_id,
|
||||
default_brain=True,
|
||||
)
|
||||
|
||||
def delete_brain_users(self, brain_id: UUID) -> None:
|
||||
self.brain_user_repository.delete_brain_subscribers(
|
||||
brain_id=brain_id,
|
||||
|
@ -5,22 +5,6 @@ from modules.brain.service.brain_user_service import BrainUserService
|
||||
|
||||
brain_user_service = BrainUserService()
|
||||
|
||||
|
||||
def test_retrieve_default_brain(client, api_key):
|
||||
# Making a GET request to the /brains/default/ endpoint
|
||||
response = client.get(
|
||||
"/brains/default/",
|
||||
headers={"Authorization": "Bearer " + api_key},
|
||||
)
|
||||
|
||||
# Assert that the response status code is 200 (HTTP OK)
|
||||
assert response.status_code == 200
|
||||
|
||||
default_brain = response.json()
|
||||
assert "id" in default_brain
|
||||
assert "name" in default_brain
|
||||
|
||||
|
||||
def test_create_brain(client, api_key):
|
||||
# Generate a random name for the brain
|
||||
random_brain_name = "".join(
|
||||
|
@ -39,8 +39,7 @@ if (
|
||||
|
||||
// This wrapper is used to make effect calls at a high level in app rendering.
|
||||
const App = ({ children }: PropsWithChildren): JSX.Element => {
|
||||
const { fetchAllBrains, fetchDefaultBrain, fetchPublicPrompts } =
|
||||
useBrainContext();
|
||||
const { fetchAllBrains, fetchPublicPrompts } = useBrainContext();
|
||||
const { onClickOutside } = useOutsideClickListener();
|
||||
const { session } = useSupabase();
|
||||
|
||||
@ -49,7 +48,7 @@ const App = ({ children }: PropsWithChildren): JSX.Element => {
|
||||
useEffect(() => {
|
||||
if (session?.user) {
|
||||
void fetchAllBrains();
|
||||
void fetchDefaultBrain();
|
||||
|
||||
void fetchPublicPrompts();
|
||||
posthog.identify(session.user.id, { email: session.user.email });
|
||||
posthog.startSessionRecording();
|
||||
|
@ -4,6 +4,7 @@
|
||||
@use "@/styles/Spacings.module.scss";
|
||||
@use "@/styles/Typography.module.scss";
|
||||
@use "@/styles/Variables.module.scss";
|
||||
@use "@/styles/ZIndexes.module.scss";
|
||||
|
||||
.main_container {
|
||||
position: relative;
|
||||
@ -71,3 +72,21 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.onboarding_overlay {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: ZIndexes.$overlay;
|
||||
background-color: var(--background-blur);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
.first_brain_button {
|
||||
position: absolute;
|
||||
right: Spacings.$spacing07;
|
||||
top: Spacings.$spacing04;
|
||||
display: flex;
|
||||
gap: Spacings.$spacing05;
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,12 @@ import { useBrainCreationContext } from "@/lib/components/AddBrainModal/brainCre
|
||||
import { OnboardingModal } from "@/lib/components/OnboardingModal/OnboardingModal";
|
||||
import PageHeader from "@/lib/components/PageHeader/PageHeader";
|
||||
import { UploadDocumentModal } from "@/lib/components/UploadDocumentModal/UploadDocumentModal";
|
||||
import { MessageInfoBox } from "@/lib/components/ui/MessageInfoBox/MessageInfoBox";
|
||||
import QuivrButton from "@/lib/components/ui/QuivrButton/QuivrButton";
|
||||
import { SearchBar } from "@/lib/components/ui/SearchBar/SearchBar";
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext";
|
||||
import { useUserData } from "@/lib/hooks/useUserData";
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
import { ButtonType } from "@/lib/types/QuivrButton";
|
||||
|
||||
@ -19,7 +22,9 @@ import styles from "./page.module.scss";
|
||||
const Search = (): JSX.Element => {
|
||||
const pathname = usePathname();
|
||||
const { session } = useSupabase();
|
||||
const { setIsBrainCreationModalOpened } = useBrainCreationContext();
|
||||
const { isBrainCreationModalOpened, setIsBrainCreationModalOpened } =
|
||||
useBrainCreationContext();
|
||||
const { userIdentityData } = useUserData();
|
||||
const { isDarkMode } = useUserSettingsContext();
|
||||
|
||||
useEffect(() => {
|
||||
@ -40,35 +45,54 @@ const Search = (): JSX.Element => {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles.main_container}>
|
||||
<div className={styles.page_header}>
|
||||
<PageHeader iconName="home" label="Home" buttons={buttons} />
|
||||
</div>
|
||||
<div className={styles.search_page_container}>
|
||||
<div className={styles.main_wrapper}>
|
||||
<div className={styles.quivr_logo_wrapper}>
|
||||
<QuivrLogo size={80} color={isDarkMode ? "white" : "black"} />
|
||||
<div className={styles.quivr_text}>
|
||||
<span>Talk to </span>
|
||||
<span className={styles.quivr_text_primary}>Quivr</span>
|
||||
<>
|
||||
<div className={styles.main_container}>
|
||||
<div className={styles.page_header}>
|
||||
<PageHeader iconName="home" label="Home" buttons={buttons} />
|
||||
</div>
|
||||
<div className={styles.search_page_container}>
|
||||
<div className={styles.main_wrapper}>
|
||||
<div className={styles.quivr_logo_wrapper}>
|
||||
<QuivrLogo size={80} color={isDarkMode ? "white" : "black"} />
|
||||
<div className={styles.quivr_text}>
|
||||
<span>Talk to </span>
|
||||
<span className={styles.quivr_text_primary}>Quivr</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.search_bar_wrapper}>
|
||||
<SearchBar />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.search_bar_wrapper}>
|
||||
<SearchBar />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.shortcuts_card_wrapper}>
|
||||
<div className={styles.shortcut_wrapper}>
|
||||
<span>Press</span>
|
||||
<span className={styles.shortcut}>@</span>
|
||||
<span>to select a brain</span>
|
||||
<div className={styles.shortcuts_card_wrapper}>
|
||||
<div className={styles.shortcut_wrapper}>
|
||||
<span>Press</span>
|
||||
<span className={styles.shortcut}>@</span>
|
||||
<span>to select a brain</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<UploadDocumentModal />
|
||||
<AddBrainModal />
|
||||
<OnboardingModal />
|
||||
</div>
|
||||
<UploadDocumentModal />
|
||||
<AddBrainModal />
|
||||
<OnboardingModal />
|
||||
</div>
|
||||
{!isBrainCreationModalOpened && !userIdentityData?.onboarded && (
|
||||
<div className={styles.onboarding_overlay}>
|
||||
<div className={styles.first_brain_button}>
|
||||
<MessageInfoBox type="tutorial">
|
||||
<span>Press the following button to create your first brain</span>
|
||||
</MessageInfoBox>
|
||||
<QuivrButton
|
||||
iconName="brain"
|
||||
label="Create Brain"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setIsBrainCreationModalOpened(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
import { Brain } from "@/lib/context/BrainProvider/types";
|
||||
import { useUrlBrain } from "@/lib/hooks/useBrainIdFromUrl";
|
||||
import { BrainConfig, Model } from "@/lib/types/BrainConfig";
|
||||
@ -14,8 +13,6 @@ import { useBrainFetcher } from "../../../hooks/useBrainFetcher";
|
||||
export const useBrainFormState = () => {
|
||||
const { brainId } = useUrlBrain();
|
||||
|
||||
const { defaultBrainId } = useBrainContext();
|
||||
|
||||
const {
|
||||
register,
|
||||
getValues,
|
||||
@ -30,7 +27,6 @@ export const useBrainFormState = () => {
|
||||
brainId,
|
||||
});
|
||||
|
||||
const isDefaultBrain = defaultBrainId === brainId;
|
||||
const promptId = watch("prompt_id");
|
||||
const openAiKey = watch("openAiKey");
|
||||
const model = watch("model");
|
||||
@ -80,7 +76,6 @@ export const useBrainFormState = () => {
|
||||
model,
|
||||
temperature,
|
||||
maxTokens,
|
||||
isDefaultBrain,
|
||||
promptId,
|
||||
openAiKey,
|
||||
defaultValues,
|
||||
|
@ -23,14 +23,13 @@ type UseSettingsTabProps = {
|
||||
export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => {
|
||||
const { t } = useTranslation(["translation", "brain", "config"]);
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [isSettingAsDefault, setIsSettingAsDefault] = useState(false);
|
||||
const { publish } = useToast();
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const { setAsDefaultBrain, updateBrain } = useBrainApi();
|
||||
const { fetchAllBrains, fetchDefaultBrain } = useBrainContext();
|
||||
const { updateBrain } = useBrainApi();
|
||||
const { fetchAllBrains } = useBrainContext();
|
||||
const { userData } = useUserData();
|
||||
|
||||
const { getValues, maxTokens, setValue, openAiKey, model, isDefaultBrain } =
|
||||
const { getValues, maxTokens, setValue, openAiKey, model } =
|
||||
useBrainFormState();
|
||||
|
||||
const accessibleModels = getAccessibleModels({
|
||||
@ -57,38 +56,6 @@ export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => {
|
||||
};
|
||||
}, [formRef.current]);
|
||||
|
||||
const setAsDefaultBrainHandler = async () => {
|
||||
try {
|
||||
setIsSettingAsDefault(true);
|
||||
await setAsDefaultBrain(brainId);
|
||||
publish({
|
||||
variant: "success",
|
||||
text: t("defaultBrainSet", { ns: "config" }),
|
||||
});
|
||||
void fetchAllBrains();
|
||||
void fetchDefaultBrain();
|
||||
} catch (err) {
|
||||
// ...
|
||||
|
||||
if (isAxiosError(err) && err.response?.status === 429) {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: `${JSON.stringify(
|
||||
(
|
||||
err.response as {
|
||||
data: { detail: string };
|
||||
}
|
||||
).data.detail
|
||||
)}`,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
setIsSettingAsDefault(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const { name, description } = getValues();
|
||||
|
||||
@ -142,10 +109,7 @@ export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => {
|
||||
|
||||
return {
|
||||
handleSubmit,
|
||||
setAsDefaultBrainHandler,
|
||||
isUpdating,
|
||||
isSettingAsDefault,
|
||||
isDefaultBrain,
|
||||
formRef,
|
||||
accessibleModels,
|
||||
setIsUpdating,
|
||||
|
@ -51,15 +51,6 @@ export const deleteBrain = async (
|
||||
await axiosInstance.delete(`/brains/${brainId}/subscription`);
|
||||
};
|
||||
|
||||
export const getDefaultBrain = async (
|
||||
axiosInstance: AxiosInstance
|
||||
): Promise<MinimalBrainForUser | undefined> => {
|
||||
return mapBackendMinimalBrainToMinimalBrain(
|
||||
(await axiosInstance.get<BackendMinimalBrainForUser>(`/brains/default/`))
|
||||
.data
|
||||
);
|
||||
};
|
||||
|
||||
export const getBrains = async (
|
||||
axiosInstance: AxiosInstance
|
||||
): Promise<MinimalBrainForUser[]> => {
|
||||
@ -113,13 +104,6 @@ export const updateBrainAccess = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const setAsDefaultBrain = async (
|
||||
brainId: string,
|
||||
axiosInstance: AxiosInstance
|
||||
): Promise<void> => {
|
||||
await axiosInstance.post(`/brains/${brainId}/default`);
|
||||
};
|
||||
|
||||
export const updateBrain = async (
|
||||
brainId: string,
|
||||
brain: UpdateBrainInput,
|
||||
|
@ -92,6 +92,7 @@ export type IntegrationBrains = {
|
||||
tags: IntegrationBrainTag[];
|
||||
information: string;
|
||||
integration_display_name: string;
|
||||
onboarding_brain: boolean;
|
||||
};
|
||||
|
||||
export type UpdateBrainInput = Partial<CreateBrainInput>;
|
||||
|
@ -7,11 +7,9 @@ import {
|
||||
getBrain,
|
||||
getBrains,
|
||||
getBrainUsers,
|
||||
getDefaultBrain,
|
||||
getDocsFromQuestion,
|
||||
getIntegrationBrains,
|
||||
getPublicBrains,
|
||||
setAsDefaultBrain,
|
||||
Subscription,
|
||||
updateBrain,
|
||||
updateBrainAccess,
|
||||
@ -31,7 +29,6 @@ export const useBrainApi = () => {
|
||||
createBrain: async (brain: CreateBrainInput) =>
|
||||
createBrain(brain, axiosInstance),
|
||||
deleteBrain: async (id: string) => deleteBrain(id, axiosInstance),
|
||||
getDefaultBrain: async () => getDefaultBrain(axiosInstance),
|
||||
getBrains: async () => getBrains(axiosInstance),
|
||||
getBrain: async (id: string) => getBrain(id, axiosInstance),
|
||||
addBrainSubscriptions: async (
|
||||
@ -45,8 +42,6 @@ export const useBrainApi = () => {
|
||||
userEmail: string,
|
||||
subscription: SubscriptionUpdatableProperties
|
||||
) => updateBrainAccess(brainId, userEmail, subscription, axiosInstance),
|
||||
setAsDefaultBrain: async (brainId: string) =>
|
||||
setAsDefaultBrain(brainId, axiosInstance),
|
||||
updateBrain: async (brainId: string, brain: UpdateBrainInput) =>
|
||||
updateBrain(brainId, brain, axiosInstance),
|
||||
getPublicBrains: async () => getPublicBrains(axiosInstance),
|
||||
|
@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Modal } from "@/lib/components/ui/Modal/Modal";
|
||||
import { addBrainDefaultValues } from "@/lib/config/defaultBrainConfig";
|
||||
import { useUserData } from "@/lib/hooks/useUserData";
|
||||
|
||||
import styles from "./AddBrainModal.module.scss";
|
||||
import { useBrainCreationContext } from "./brainCreation-provider";
|
||||
@ -15,6 +16,7 @@ import { CreateBrainProps } from "./types/types";
|
||||
|
||||
export const AddBrainModal = (): JSX.Element => {
|
||||
const { t } = useTranslation(["translation", "brain", "config"]);
|
||||
const { userIdentityData } = useUserData();
|
||||
|
||||
const {
|
||||
isBrainCreationModalOpened,
|
||||
@ -43,6 +45,7 @@ export const AddBrainModal = (): JSX.Element => {
|
||||
desc={t("newBrainSubtitle", { ns: "brain" })}
|
||||
isOpen={isBrainCreationModalOpened}
|
||||
setOpen={setIsBrainCreationModalOpened}
|
||||
unclosable={!userIdentityData?.onboarded}
|
||||
size="big"
|
||||
CloseTrigger={<div />}
|
||||
>
|
||||
|
@ -24,6 +24,11 @@
|
||||
flex-direction: column;
|
||||
gap: Spacings.$spacing02;
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.tag_wrapper {
|
||||
height: 2rem;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { MessageInfoBox } from "@/lib/components/ui/MessageInfoBox/MessageInfoBo
|
||||
import { Tag } from "@/lib/components/ui/Tag/Tag";
|
||||
import Tooltip from "@/lib/components/ui/Tooltip/Tooltip";
|
||||
import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext";
|
||||
import { useUserData } from "@/lib/hooks/useUserData";
|
||||
|
||||
import styles from "./BrainCatalogue.module.scss";
|
||||
|
||||
@ -21,6 +22,7 @@ export const BrainCatalogue = ({
|
||||
const { setCurrentSelectedBrain, currentSelectedBrain } =
|
||||
useBrainCreationContext();
|
||||
const { isDarkMode } = useUserSettingsContext();
|
||||
const { userIdentityData } = useUserData();
|
||||
|
||||
return (
|
||||
<div className={styles.cards_wrapper}>
|
||||
@ -30,13 +32,26 @@ export const BrainCatalogue = ({
|
||||
use cases or data sources.
|
||||
</span>
|
||||
</MessageInfoBox>
|
||||
{!userIdentityData?.onboarded && (
|
||||
<MessageInfoBox type="tutorial">
|
||||
<span>
|
||||
Let's start by creating a Docs & URLs brain.<br></br>Of
|
||||
course, feel free to explore other types of brains during your Quivr
|
||||
journey.
|
||||
</span>
|
||||
</MessageInfoBox>
|
||||
)}
|
||||
<span className={styles.title}>Choose a brain type</span>
|
||||
<div className={styles.brains_grid}>
|
||||
{brains.map((brain) => {
|
||||
return (
|
||||
<div
|
||||
key={brain.id}
|
||||
className={styles.brain_card_container}
|
||||
className={`${styles.brain_card_container} ${
|
||||
!userIdentityData?.onboarded && !brain.onboarding_brain
|
||||
? styles.disabled
|
||||
: ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
next();
|
||||
setCurrentSelectedBrain(brain);
|
||||
|
@ -2,9 +2,12 @@ import { capitalCase } from "change-case";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { KnowledgeToFeed } from "@/app/chat/[chatId]/components/ActionsBar/components";
|
||||
import { useUserApi } from "@/lib/api/user/useUserApi";
|
||||
import { MessageInfoBox } from "@/lib/components/ui/MessageInfoBox/MessageInfoBox";
|
||||
import QuivrButton from "@/lib/components/ui/QuivrButton/QuivrButton";
|
||||
import { TextInput } from "@/lib/components/ui/TextInput/TextInput";
|
||||
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
||||
import { useUserData } from "@/lib/hooks/useUserData";
|
||||
|
||||
import styles from "./CreateBrainStep.module.scss";
|
||||
import { useBrainCreationApi } from "./hooks/useBrainCreationApi";
|
||||
@ -18,6 +21,9 @@ export const CreateBrainStep = (): JSX.Element => {
|
||||
const { creating, setCreating, currentSelectedBrain } =
|
||||
useBrainCreationContext();
|
||||
const [createBrainStepIndex, setCreateBrainStepIndex] = useState<number>(0);
|
||||
const { knowledgeToFeed } = useKnowledgeToFeedContext();
|
||||
const { userIdentityData } = useUserData();
|
||||
const { updateUserIdentity } = useUserApi();
|
||||
|
||||
useEffect(() => {
|
||||
if (currentSelectedBrain?.connection_settings) {
|
||||
@ -42,59 +48,74 @@ export const CreateBrainStep = (): JSX.Element => {
|
||||
goToPreviousStep();
|
||||
};
|
||||
|
||||
const feed = (): void => {
|
||||
const feed = async (): Promise<void> => {
|
||||
if (!userIdentityData?.onboarded) {
|
||||
await updateUserIdentity({
|
||||
...userIdentityData,
|
||||
username: userIdentityData?.username ?? "",
|
||||
onboarded: true,
|
||||
});
|
||||
}
|
||||
setCreating(true);
|
||||
createBrain();
|
||||
};
|
||||
|
||||
if (currentStepIndex !== 2) {
|
||||
return <></>;
|
||||
}
|
||||
const renderSettings = () => {
|
||||
return (
|
||||
<>
|
||||
<MessageInfoBox type="warning">
|
||||
{currentSelectedBrain?.information}
|
||||
</MessageInfoBox>
|
||||
{fields.map(({ name, value }) => (
|
||||
<TextInput
|
||||
key={name}
|
||||
inputValue={value}
|
||||
setInputValue={(inputValue) => handleInputChange(name, inputValue)}
|
||||
label={capitalCase(name)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.brain_knowledge_wrapper}>
|
||||
{!createBrainStepIndex && (
|
||||
<div className={styles.settings_wrapper}>
|
||||
<MessageInfoBox type="warning">
|
||||
{currentSelectedBrain?.information}
|
||||
const renderFeedBrain = () => {
|
||||
return (
|
||||
<>
|
||||
{!userIdentityData?.onboarded && (
|
||||
<MessageInfoBox type="tutorial">
|
||||
<span>
|
||||
Upload documents or add URLs to add knowledges to your brain.
|
||||
</span>
|
||||
</MessageInfoBox>
|
||||
{fields.map(({ name, value }) => (
|
||||
<TextInput
|
||||
key={name}
|
||||
inputValue={value}
|
||||
setInputValue={(inputValue) =>
|
||||
handleInputChange(name, inputValue)
|
||||
}
|
||||
label={capitalCase(name)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!!currentSelectedBrain?.max_files && !!createBrainStepIndex && (
|
||||
)}
|
||||
<div>
|
||||
<span className={styles.title}>Feed your brain</span>
|
||||
<KnowledgeToFeed hideBrainSelector={true} />
|
||||
</div>
|
||||
)}
|
||||
{!currentSelectedBrain?.max_files &&
|
||||
!currentSelectedBrain?.connection_settings && (
|
||||
<div className={styles.message_info_box_wrapper}>
|
||||
<MessageInfoBox type="info">
|
||||
<div className={styles.message_content}>
|
||||
Click on
|
||||
<QuivrButton
|
||||
label="Create"
|
||||
color="primary"
|
||||
iconName="add"
|
||||
onClick={feed}
|
||||
isLoading={creating}
|
||||
/>
|
||||
to finish your brain creation.
|
||||
</div>
|
||||
</MessageInfoBox>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderCreateButton = () => {
|
||||
return (
|
||||
<MessageInfoBox type="info">
|
||||
<div className={styles.message_content}>
|
||||
Click on
|
||||
<QuivrButton
|
||||
label="Create"
|
||||
color="primary"
|
||||
iconName="add"
|
||||
onClick={feed}
|
||||
isLoading={creating}
|
||||
/>
|
||||
to finish your brain creation.
|
||||
</div>
|
||||
</MessageInfoBox>
|
||||
);
|
||||
};
|
||||
|
||||
const renderButtons = () => {
|
||||
return (
|
||||
<div className={styles.buttons_wrapper}>
|
||||
<QuivrButton
|
||||
label="Previous step"
|
||||
@ -109,6 +130,9 @@ export const CreateBrainStep = (): JSX.Element => {
|
||||
color="primary"
|
||||
iconName="add"
|
||||
onClick={feed}
|
||||
disabled={
|
||||
knowledgeToFeed.length === 0 && !userIdentityData?.onboarded
|
||||
}
|
||||
isLoading={creating}
|
||||
/>
|
||||
) : (
|
||||
@ -121,6 +145,24 @@ export const CreateBrainStep = (): JSX.Element => {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (currentStepIndex !== 2) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.brain_knowledge_wrapper}>
|
||||
{!createBrainStepIndex && renderSettings()}
|
||||
{!!currentSelectedBrain?.max_files &&
|
||||
!!createBrainStepIndex &&
|
||||
renderFeedBrain()}
|
||||
{!currentSelectedBrain?.max_files &&
|
||||
!currentSelectedBrain?.connection_settings &&
|
||||
renderCreateButton()}
|
||||
|
||||
{renderButtons()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -46,7 +46,7 @@ export const OnboardingModal = (): JSX.Element => {
|
||||
await updateUserIdentity({
|
||||
username: methods.getValues("username"),
|
||||
company: methods.getValues("companyName"),
|
||||
onboarded: true,
|
||||
onboarded: false,
|
||||
company_size: methods.getValues("companySize"),
|
||||
usage_purpose: methods.getValues("usagePurpose") as
|
||||
| UsagePurpose
|
||||
|
@ -29,6 +29,12 @@
|
||||
background-color: var(--warning-lightest);
|
||||
}
|
||||
|
||||
&.tutorial {
|
||||
border-color: var(--gold);
|
||||
color: var(--gold);
|
||||
background-color: var(--gold);
|
||||
}
|
||||
|
||||
&.dark {
|
||||
background-color: var(--background-special-0);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import { Icon } from "../Icon/Icon";
|
||||
|
||||
export type MessageInfoBoxProps = {
|
||||
children: React.ReactNode;
|
||||
type: "info" | "success" | "warning" | "error";
|
||||
type: "info" | "success" | "warning" | "error" | "tutorial";
|
||||
unforceWhite?: boolean;
|
||||
};
|
||||
|
||||
@ -28,6 +28,8 @@ export const MessageInfoBox = ({
|
||||
return { iconName: "check", iconColor: "success" };
|
||||
case "warning":
|
||||
return { iconName: "warning", iconColor: "warning" };
|
||||
case "tutorial":
|
||||
return { iconName: "step", iconColor: "gold" };
|
||||
default:
|
||||
return { iconName: "info", iconColor: "primary" };
|
||||
}
|
||||
|
@ -29,8 +29,8 @@ export const QuivrButton = ({
|
||||
${isDarkMode ? styles.dark : ""}
|
||||
${hidden ? styles.hidden : ""}
|
||||
`}
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises, @typescript-eslint/prefer-optional-chain, @typescript-eslint/no-unnecessary-condition
|
||||
onClick={() => onClick && onClick()}
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onClick={() => onClick?.()}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
|
@ -67,7 +67,7 @@ export const SearchBar = ({
|
||||
message={message}
|
||||
setMessage={setMessage}
|
||||
onSubmit={() => void submit()}
|
||||
placeholder="Search"
|
||||
placeholder="Ask a question..."
|
||||
></Editor>
|
||||
{searching ? (
|
||||
<LoaderIcon size="big" color="accent" />
|
||||
|
@ -17,14 +17,12 @@ import { MinimalBrainForUser } from "../types";
|
||||
export const useBrainProvider = () => {
|
||||
const { publish } = useToast();
|
||||
const { track } = useEventTracking();
|
||||
const { createBrain, deleteBrain, getBrains, getDefaultBrain } =
|
||||
useBrainApi();
|
||||
const { createBrain, deleteBrain, getBrains } = useBrainApi();
|
||||
const { getPublicPrompts } = usePromptApi();
|
||||
const { t } = useTranslation(["delete_or_unsubscribe_from_brain"]);
|
||||
|
||||
const [allBrains, setAllBrains] = useState<MinimalBrainForUser[]>([]);
|
||||
const [currentBrainId, setCurrentBrainId] = useState<null | UUID>(null);
|
||||
const [defaultBrainId, setDefaultBrainId] = useState<UUID>();
|
||||
const [isFetchingBrains, setIsFetchingBrains] = useState(true);
|
||||
const [publicPrompts, setPublicPrompts] = useState<Prompt[]>([]);
|
||||
const [currentPromptId, setCurrentPromptId] = useState<null | string>(null);
|
||||
@ -87,13 +85,6 @@ export const useBrainProvider = () => {
|
||||
[deleteBrain, publish, track]
|
||||
);
|
||||
|
||||
const fetchDefaultBrain = useCallback(async () => {
|
||||
const userDefaultBrain = await getDefaultBrain();
|
||||
if (userDefaultBrain !== undefined) {
|
||||
setDefaultBrainId(userDefaultBrain.id);
|
||||
}
|
||||
}, [currentBrainId, getDefaultBrain]);
|
||||
|
||||
const fetchPublicPrompts = useCallback(async () => {
|
||||
setPublicPrompts(await getPublicPrompts());
|
||||
}, [getPublicPrompts]);
|
||||
@ -108,9 +99,6 @@ export const useBrainProvider = () => {
|
||||
currentBrainId,
|
||||
setCurrentBrainId,
|
||||
|
||||
defaultBrainId,
|
||||
fetchDefaultBrain,
|
||||
|
||||
fetchPublicPrompts,
|
||||
publicPrompts,
|
||||
currentPrompt,
|
||||
|
@ -21,7 +21,7 @@ export const OnboardingProvider = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (userIdentityData) {
|
||||
setIsOnboardingModalOpened(!userIdentityData.onboarded);
|
||||
setIsOnboardingModalOpened(!userIdentityData.username);
|
||||
}
|
||||
}, [userIdentityData]);
|
||||
|
||||
|
@ -38,6 +38,7 @@ import {
|
||||
import {
|
||||
IoArrowUpCircleOutline,
|
||||
IoCloudDownloadOutline,
|
||||
IoFootsteps,
|
||||
IoHomeOutline,
|
||||
IoSettingsSharp,
|
||||
IoShareSocial,
|
||||
@ -123,6 +124,7 @@ export const iconList: { [name: string]: IconType } = {
|
||||
share: IoShareSocial,
|
||||
software: CgSoftwareDownload,
|
||||
star: FaRegStar,
|
||||
step: IoFootsteps,
|
||||
sun: FaSun,
|
||||
thumbsDown: FaRegThumbsDown,
|
||||
thumbsUp: FaRegThumbsUp,
|
||||
|
3
supabase/migrations/20240329212126_onboarding_brain.sql
Normal file
3
supabase/migrations/20240329212126_onboarding_brain.sql
Normal file
@ -0,0 +1,3 @@
|
||||
alter table "public"."integrations" add column "onboarding_brain" boolean default false;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user