mirror of
https://github.com/StanGirard/quivr.git
synced 2024-11-11 00:23:17 +03:00
feat(frontend): new modal for add knowledge (#2173)
# 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):
This commit is contained in:
parent
70b2b58018
commit
cf61ce8132
@ -0,0 +1,62 @@
|
|||||||
|
@use "@/styles/Colors.module.scss";
|
||||||
|
@use "@/styles/ScreenSizes.module.scss";
|
||||||
|
@use "@/styles/Spacings.module.scss";
|
||||||
|
@use "@/styles/Typography.module.scss";
|
||||||
|
|
||||||
|
.knowledge_to_feed_wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-block: Spacings.$spacing05;
|
||||||
|
width: 100%;
|
||||||
|
gap: Spacings.$spacing05;
|
||||||
|
|
||||||
|
.single_selector_wrapper {
|
||||||
|
width: 30%;
|
||||||
|
min-width: 250px;
|
||||||
|
|
||||||
|
@media (max-width: ScreenSizes.$small) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs_content_wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploaded_knowledges_title {
|
||||||
|
color: Colors.$dark-grey;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploaded_knowledges {
|
||||||
|
padding: Spacings.$spacing03;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
overflow: scroll;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: Spacings.$spacing02;
|
||||||
|
overflow: scroll;
|
||||||
|
|
||||||
|
.uploaded_knowledge {
|
||||||
|
display: flex;
|
||||||
|
gap: Spacings.$spacing02;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: Spacings.$spacing02;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
@include Typography.EllipsisOverflow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,70 +1,99 @@
|
|||||||
import Link from "next/link";
|
import { useMemo, useState } from "react";
|
||||||
import { useMemo } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
import { ApiBrainSecretsInputs } from "@/lib/components/ApiBrainSecretsInputs/ApiBrainSecretsInputs";
|
import { Icon } from "@/lib/components/ui/Icon/Icon";
|
||||||
import { KnowledgeToFeedInput } from "@/lib/components/KnowledgeToFeedInput";
|
import { SingleSelector } from "@/lib/components/ui/SingleSelector/SingleSelector";
|
||||||
import Button from "@/lib/components/ui/Button";
|
import { Tabs } from "@/lib/components/ui/Tabs/Tabs";
|
||||||
import { Select } from "@/lib/components/ui/Select";
|
|
||||||
import { requiredRolesForUpload } from "@/lib/config/upload";
|
import { requiredRolesForUpload } from "@/lib/config/upload";
|
||||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||||
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
||||||
|
import { Tab } from "@/lib/types/Tab";
|
||||||
|
|
||||||
import { useFeedBrainInChat } from "./hooks/useFeedBrainInChat";
|
import styles from "./KnowledgeToFeed.module.scss";
|
||||||
|
import { FromDocuments } from "./components/FromDocuments/FromDocuments";
|
||||||
|
import { FromWebsites } from "./components/FromWebsites/FromWebsites";
|
||||||
import { formatMinimalBrainsToSelectComponentInput } from "./utils/formatMinimalBrainsToSelectComponentInput";
|
import { formatMinimalBrainsToSelectComponentInput } from "./utils/formatMinimalBrainsToSelectComponentInput";
|
||||||
|
|
||||||
type KnowledgeToFeedProps = {
|
export const KnowledgeToFeed = (): JSX.Element => {
|
||||||
dispatchHasPendingRequests: () => void;
|
const { allBrains, setCurrentBrainId, currentBrain } = useBrainContext();
|
||||||
};
|
const [selectedTab, setSelectedTab] = useState("From documents");
|
||||||
export const KnowledgeToFeed = ({
|
const { knowledgeToFeed, removeKnowledgeToFeed } =
|
||||||
dispatchHasPendingRequests,
|
useKnowledgeToFeedContext();
|
||||||
}: KnowledgeToFeedProps): JSX.Element => {
|
|
||||||
const { allBrains, currentBrainId, setCurrentBrainId } = useBrainContext();
|
|
||||||
|
|
||||||
const { t } = useTranslation(["upload", "brain"]);
|
const brainsWithUploadRights = formatMinimalBrainsToSelectComponentInput(
|
||||||
|
useMemo(
|
||||||
const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext();
|
() =>
|
||||||
const { currentBrainDetails } = useBrainContext();
|
allBrains.filter((brain) =>
|
||||||
const brainsWithUploadRights = useMemo(
|
requiredRolesForUpload.includes(brain.role)
|
||||||
() =>
|
),
|
||||||
allBrains.filter((brain) => requiredRolesForUpload.includes(brain.role)),
|
[allBrains]
|
||||||
[allBrains]
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const { feedBrain } = useFeedBrainInChat({
|
const knowledgesTabs: Tab[] = [
|
||||||
dispatchHasPendingRequests,
|
{
|
||||||
});
|
label: "From documents",
|
||||||
|
isSelected: selectedTab === "From documents",
|
||||||
|
onClick: () => setSelectedTab("From documents"),
|
||||||
|
iconName: "file",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "From websites",
|
||||||
|
isSelected: selectedTab === "From websites",
|
||||||
|
onClick: () => setSelectedTab("From websites"),
|
||||||
|
iconName: "website",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-col w-full relative pt-3" data-testid="feed-card">
|
<div className={styles.knowledge_to_feed_wrapper}>
|
||||||
<div className="flex justify-center">
|
<div className={styles.single_selector_wrapper}>
|
||||||
<Select
|
<SingleSelector
|
||||||
options={formatMinimalBrainsToSelectComponentInput(
|
options={brainsWithUploadRights}
|
||||||
brainsWithUploadRights
|
onChange={setCurrentBrainId}
|
||||||
)}
|
selectedOption={
|
||||||
emptyLabel={t("selected_brain_select_label")}
|
currentBrain
|
||||||
value={currentBrainId ?? undefined}
|
? { label: currentBrain.name, value: currentBrain.id }
|
||||||
onChange={(newSelectedBrainId) =>
|
: undefined
|
||||||
setCurrentBrainId(newSelectedBrainId)
|
|
||||||
}
|
}
|
||||||
className="flex flex-row items-center"
|
placeholder="Select a brain"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{currentBrainDetails?.brain_type === "api" ? (
|
<Tabs tabList={knowledgesTabs} />
|
||||||
<ApiBrainSecretsInputs
|
<div className={styles.tabs_content_wrapper}>
|
||||||
brainId={currentBrainDetails.id}
|
{selectedTab === "From documents" && <FromDocuments />}
|
||||||
onUpdate={() => setShouldDisplayFeedCard(false)}
|
{selectedTab === "From websites" && <FromWebsites />}
|
||||||
/>
|
</div>
|
||||||
) : (
|
<div>
|
||||||
<KnowledgeToFeedInput feedBrain={() => void feedBrain()} />
|
<div className={styles.uploaded_knowledges_title}>
|
||||||
)}
|
<span>Uploaded knowledges</span>
|
||||||
{Boolean(currentBrainId) && (
|
<span>{knowledgeToFeed.length}</span>
|
||||||
<Link href={`/studio/${currentBrainId ?? ""}`}>
|
</div>
|
||||||
<Button variant={"tertiary"}>
|
<div className={styles.uploaded_knowledges}>
|
||||||
{t("manage_brain", { ns: "brain" })}
|
{knowledgeToFeed.map((knowledge, index) => (
|
||||||
</Button>
|
<div className={styles.uploaded_knowledge} key={index}>
|
||||||
</Link>
|
<div className={styles.left}>
|
||||||
)}
|
<Icon
|
||||||
|
name={knowledge.source === "crawl" ? "website" : "file"}
|
||||||
|
size="small"
|
||||||
|
color="black"
|
||||||
|
/>
|
||||||
|
<span className={styles.label}>
|
||||||
|
{knowledge.source === "crawl"
|
||||||
|
? knowledge.url
|
||||||
|
: knowledge.file.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Icon
|
||||||
|
name="delete"
|
||||||
|
size="normal"
|
||||||
|
color="dangerous"
|
||||||
|
handleHover={true}
|
||||||
|
onClick={() => removeKnowledgeToFeed(index)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
@use "@/styles/Colors.module.scss";
|
||||||
|
@use "@/styles/Radius.module.scss";
|
||||||
|
@use "@/styles/Spacings.module.scss";
|
||||||
|
|
||||||
|
.from_document_wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
column-gap: Spacings.$spacing05;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px dashed Colors.$lighter-grey;
|
||||||
|
border-radius: Radius.$big;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&.dragging {
|
||||||
|
border: 3px dashed Colors.$accent;
|
||||||
|
background-color: Colors.$lightest-black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
padding: Spacings.$spacing05;
|
||||||
|
display: flex;
|
||||||
|
gap: Spacings.$spacing02;
|
||||||
|
|
||||||
|
.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { Icon } from "@/lib/components/ui/Icon/Icon";
|
||||||
|
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
||||||
|
import { useCustomDropzone } from "@/lib/hooks/useDropzone";
|
||||||
|
|
||||||
|
import styles from "./FromDocuments.module.scss";
|
||||||
|
|
||||||
|
export const FromDocuments = (): JSX.Element => {
|
||||||
|
const [dragging, setDragging] = useState<boolean>(false);
|
||||||
|
const { getRootProps, getInputProps, open } = useCustomDropzone();
|
||||||
|
const { knowledgeToFeed } = useKnowledgeToFeedContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDragging(false);
|
||||||
|
}, [knowledgeToFeed]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
${styles.from_document_wrapper}
|
||||||
|
${dragging ? styles.dragging : ""}
|
||||||
|
`}
|
||||||
|
{...getRootProps()}
|
||||||
|
onDragOver={() => setDragging(true)}
|
||||||
|
onDragLeave={() => setDragging(false)}
|
||||||
|
onMouseLeave={() => setDragging(false)}
|
||||||
|
>
|
||||||
|
<Icon name="upload" size="big" color={dragging ? "accent" : "black"} />
|
||||||
|
<div className={styles.input} onClick={open}>
|
||||||
|
<div className={styles.clickable}>
|
||||||
|
<span>Choose files</span>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
</div>
|
||||||
|
<span>or drag it here</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,20 @@
|
|||||||
|
import { useCrawler } from "@/lib/components/KnowledgeToFeedInput/components/Crawler/hooks/useCrawler";
|
||||||
|
import { TextInput } from "@/lib/components/ui/TextInput/TextInput";
|
||||||
|
|
||||||
|
import styles from "./FromWebsites.module.scss";
|
||||||
|
|
||||||
|
export const FromWebsites = (): JSX.Element => {
|
||||||
|
const { handleSubmit, urlToCrawl, setUrlToCrawl } = useCrawler();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.from_document_wrapper}>
|
||||||
|
<TextInput
|
||||||
|
label="Enter a website URL"
|
||||||
|
setInputValue={setUrlToCrawl}
|
||||||
|
inputValue={urlToCrawl}
|
||||||
|
iconName="followUp"
|
||||||
|
onSubmit={() => handleSubmit()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -48,7 +48,7 @@ const SelectedChatPage = (): JSX.Element => {
|
|||||||
onClick: () => {
|
onClick: () => {
|
||||||
setShouldDisplayFeedCard(true);
|
setShouldDisplayFeedCard(true);
|
||||||
},
|
},
|
||||||
iconName: "upload",
|
iconName: "uploadFile",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Manage current brain",
|
label: "Manage current brain",
|
||||||
|
@ -8,7 +8,6 @@ import { useBrainCreationContext } from "@/lib/components/AddBrainModal/componen
|
|||||||
import PageHeader from "@/lib/components/PageHeader/PageHeader";
|
import PageHeader from "@/lib/components/PageHeader/PageHeader";
|
||||||
import { UploadDocumentModal } from "@/lib/components/UploadDocumentModal/UploadDocumentModal";
|
import { UploadDocumentModal } from "@/lib/components/UploadDocumentModal/UploadDocumentModal";
|
||||||
import { SearchBar } from "@/lib/components/ui/SearchBar/SearchBar";
|
import { SearchBar } from "@/lib/components/ui/SearchBar/SearchBar";
|
||||||
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
|
||||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||||
import { ButtonType } from "@/lib/types/QuivrButton";
|
import { ButtonType } from "@/lib/types/QuivrButton";
|
||||||
@ -18,7 +17,6 @@ import styles from "./page.module.scss";
|
|||||||
const Search = (): JSX.Element => {
|
const Search = (): JSX.Element => {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const { session } = useSupabase();
|
const { session } = useSupabase();
|
||||||
const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext();
|
|
||||||
const { setIsBrainCreationModalOpened } = useBrainCreationContext();
|
const { setIsBrainCreationModalOpened } = useBrainCreationContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -36,14 +34,6 @@ const Search = (): JSX.Element => {
|
|||||||
},
|
},
|
||||||
iconName: "brain",
|
iconName: "brain",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "Add knowledge",
|
|
||||||
color: "primary",
|
|
||||||
onClick: () => {
|
|
||||||
setShouldDisplayFeedCard(true);
|
|
||||||
},
|
|
||||||
iconName: "upload",
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -2,6 +2,7 @@ import { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { useChatApi } from "@/lib/api/chat/useChatApi";
|
import { useChatApi } from "@/lib/api/chat/useChatApi";
|
||||||
|
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||||
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
||||||
import { useToast } from "@/lib/hooks";
|
import { useToast } from "@/lib/hooks";
|
||||||
import { useUrlBrain } from "@/lib/hooks/useBrainIdFromUrl";
|
import { useUrlBrain } from "@/lib/hooks/useBrainIdFromUrl";
|
||||||
@ -18,13 +19,17 @@ export const useFeedBrain = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { publish } = useToast();
|
const { publish } = useToast();
|
||||||
const { t } = useTranslation(["upload"]);
|
const { t } = useTranslation(["upload"]);
|
||||||
const { brainId } = useUrlBrain();
|
let { brainId } = useUrlBrain();
|
||||||
const { setKnowledgeToFeed, knowledgeToFeed } = useKnowledgeToFeedContext();
|
const { currentBrainId } = useBrainContext();
|
||||||
|
const { setKnowledgeToFeed, knowledgeToFeed, setShouldDisplayFeedCard } =
|
||||||
|
useKnowledgeToFeedContext();
|
||||||
const [hasPendingRequests, setHasPendingRequests] = useState(false);
|
const [hasPendingRequests, setHasPendingRequests] = useState(false);
|
||||||
const { handleFeedBrain } = useFeedBrainHandler();
|
const { handleFeedBrain } = useFeedBrainHandler();
|
||||||
|
|
||||||
const { createChat, deleteChat } = useChatApi();
|
const { createChat, deleteChat } = useChatApi();
|
||||||
|
|
||||||
const feedBrain = async (): Promise<void> => {
|
const feedBrain = async (): Promise<void> => {
|
||||||
|
brainId ??= currentBrainId ?? undefined;
|
||||||
if (brainId === undefined) {
|
if (brainId === undefined) {
|
||||||
publish({
|
publish({
|
||||||
variant: "danger",
|
variant: "danger",
|
||||||
@ -50,6 +55,7 @@ export const useFeedBrain = ({
|
|||||||
dispatchHasPendingRequests?.();
|
dispatchHasPendingRequests?.();
|
||||||
closeFeedInput?.();
|
closeFeedInput?.();
|
||||||
setHasPendingRequests(true);
|
setHasPendingRequests(true);
|
||||||
|
setShouldDisplayFeedCard(false);
|
||||||
await handleFeedBrain({
|
await handleFeedBrain({
|
||||||
brainId,
|
brainId,
|
||||||
chatId: currentChatId,
|
chatId: currentChatId,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import Button from "@/lib/components/ui/Button";
|
import Button from "@/lib/components/ui/Button";
|
||||||
import { Modal } from "@/lib/components/ui/Modal";
|
import { Modal } from "@/lib/components/ui/Modal/Modal";
|
||||||
|
|
||||||
type DeleteOrUnsubscribeConfirmationModalProps = {
|
type DeleteOrUnsubscribeConfirmationModalProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import Button from "@/lib/components/ui/Button";
|
import Button from "@/lib/components/ui/Button";
|
||||||
import { Modal } from "@/lib/components/ui/Modal";
|
import { Modal } from "@/lib/components/ui/Modal/Modal";
|
||||||
|
|
||||||
import { useBrainFormState } from "../../hooks/useBrainFormState";
|
import { useBrainFormState } from "../../hooks/useBrainFormState";
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ const Studio = (): JSX.Element => {
|
|||||||
onClick: () => {
|
onClick: () => {
|
||||||
setShouldDisplayFeedCard(true);
|
setShouldDisplayFeedCard(true);
|
||||||
},
|
},
|
||||||
iconName: "upload",
|
iconName: "uploadFile",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import PageHeader from "@/lib/components/PageHeader/PageHeader";
|
import PageHeader from "@/lib/components/PageHeader/PageHeader";
|
||||||
import { Modal } from "@/lib/components/ui/Modal";
|
import { Modal } from "@/lib/components/ui/Modal/Modal";
|
||||||
import QuivrButton from "@/lib/components/ui/QuivrButton/QuivrButton";
|
import QuivrButton from "@/lib/components/ui/QuivrButton/QuivrButton";
|
||||||
import { Tabs } from "@/lib/components/ui/Tabs/Tabs";
|
import { Tabs } from "@/lib/components/ui/Tabs/Tabs";
|
||||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { Modal } from "@/lib/components/ui/Modal";
|
import { Modal } from "@/lib/components/ui/Modal/Modal";
|
||||||
|
|
||||||
import { useBrainCreationContext } from "./brainCreation-provider";
|
import { useBrainCreationContext } from "./brainCreation-provider";
|
||||||
import { BrainKnowledgeStep } from "./components/BrainKnowledgeStep/BrainKnowledgeStep";
|
import { BrainKnowledgeStep } from "./components/BrainKnowledgeStep/BrainKnowledgeStep";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import Button from "@/lib/components/ui/Button";
|
import Button from "@/lib/components/ui/Button";
|
||||||
import { Modal } from "@/lib/components/ui/Modal";
|
import { Modal } from "@/lib/components/ui/Modal/Modal";
|
||||||
|
|
||||||
type PublicAccessConfirmationModalProps = {
|
type PublicAccessConfirmationModalProps = {
|
||||||
opened: boolean;
|
opened: boolean;
|
||||||
|
@ -1,9 +1,18 @@
|
|||||||
@use "@/styles/Colors.module.scss";
|
@use "@/styles/Colors.module.scss";
|
||||||
|
@use "@/styles/Spacings.module.scss";
|
||||||
@use "@/styles/ZIndexes.module.scss";
|
@use "@/styles/ZIndexes.module.scss";
|
||||||
|
|
||||||
.knowledge_modal {
|
.knowledge_modal {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
background-color: Colors.$white;
|
background-color: Colors.$white;
|
||||||
align-items: center;
|
width: 100%;
|
||||||
justify-content: center;
|
flex: 1;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,32 @@
|
|||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { KnowledgeToFeed } from "@/app/chat/[chatId]/components/ActionsBar/components";
|
import { KnowledgeToFeed } from "@/app/chat/[chatId]/components/ActionsBar/components";
|
||||||
import { useActionBar } from "@/app/chat/[chatId]/components/ActionsBar/hooks/useActionBar";
|
import { useAddKnowledge } from "@/app/studio/[brainId]/components/BrainManagementTabs/components/KnowledgeOrSecretsTab/components/AddKnowledge/hooks/useAddKnowledge";
|
||||||
|
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||||
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
||||||
|
|
||||||
import styles from "./UploadDocumentModal.module.scss";
|
import styles from "./UploadDocumentModal.module.scss";
|
||||||
|
|
||||||
import { Modal } from "../ui/Modal";
|
import { Modal } from "../ui/Modal/Modal";
|
||||||
|
import { QuivrButton } from "../ui/QuivrButton/QuivrButton";
|
||||||
|
|
||||||
export const UploadDocumentModal = (): JSX.Element => {
|
export const UploadDocumentModal = (): JSX.Element => {
|
||||||
const { shouldDisplayFeedCard, setShouldDisplayFeedCard } =
|
const { shouldDisplayFeedCard, setShouldDisplayFeedCard, knowledgeToFeed } =
|
||||||
useKnowledgeToFeedContext();
|
useKnowledgeToFeedContext();
|
||||||
const { setHasPendingRequests } = useActionBar();
|
const { currentBrain } = useBrainContext();
|
||||||
|
const { feedBrain } = useAddKnowledge();
|
||||||
|
const [feeding, setFeeding] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useKnowledgeToFeedContext();
|
||||||
|
const { t } = useTranslation(["knowledge"]);
|
||||||
|
|
||||||
|
const handleFeedBrain = async () => {
|
||||||
|
setFeeding(true);
|
||||||
|
await feedBrain();
|
||||||
|
setFeeding(false);
|
||||||
|
setShouldDisplayFeedCard(false);
|
||||||
|
};
|
||||||
|
|
||||||
if (!shouldDisplayFeedCard) {
|
if (!shouldDisplayFeedCard) {
|
||||||
return <></>;
|
return <></>;
|
||||||
@ -21,21 +36,23 @@ export const UploadDocumentModal = (): JSX.Element => {
|
|||||||
<Modal
|
<Modal
|
||||||
isOpen={shouldDisplayFeedCard}
|
isOpen={shouldDisplayFeedCard}
|
||||||
setOpen={setShouldDisplayFeedCard}
|
setOpen={setShouldDisplayFeedCard}
|
||||||
|
title={t("addKnowledgeTitle", { ns: "knowledge" })}
|
||||||
|
desc={t("addKnowledgeSubtitle", { ns: "knowledge" })}
|
||||||
|
bigModal={true}
|
||||||
CloseTrigger={<div />}
|
CloseTrigger={<div />}
|
||||||
>
|
>
|
||||||
<div className={styles.knowledge_modal}>
|
<div className={styles.knowledge_modal}>
|
||||||
<AnimatePresence>
|
<KnowledgeToFeed />
|
||||||
<motion.div
|
<div className={styles.button}>
|
||||||
key="slide"
|
<QuivrButton
|
||||||
initial={{ y: "100%", opacity: 0 }}
|
label="Feed Brain"
|
||||||
animate={{ y: 0, opacity: 1, transition: { duration: 0.2 } }}
|
color="primary"
|
||||||
exit={{ y: "100%", opacity: 0 }}
|
iconName="add"
|
||||||
>
|
onClick={handleFeedBrain}
|
||||||
<KnowledgeToFeed
|
disabled={knowledgeToFeed.length === 0 || !currentBrain}
|
||||||
dispatchHasPendingRequests={() => setHasPendingRequests(true)}
|
isLoading={feeding}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</div>
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
@ -2,23 +2,31 @@
|
|||||||
@use "@/styles/IconSizes.module.scss";
|
@use "@/styles/IconSizes.module.scss";
|
||||||
|
|
||||||
.small {
|
.small {
|
||||||
width: IconSizes.$small;
|
min-width: IconSizes.$small;
|
||||||
height: IconSizes.$small;
|
min-height: IconSizes.$small;
|
||||||
|
max-width: IconSizes.$small;
|
||||||
|
max-height: IconSizes.$small;
|
||||||
}
|
}
|
||||||
|
|
||||||
.normal {
|
.normal {
|
||||||
width: IconSizes.$normal;
|
min-width: IconSizes.$normal;
|
||||||
height: IconSizes.$normal;
|
min-height: IconSizes.$normal;
|
||||||
|
max-width: IconSizes.$normal;
|
||||||
|
max-height: IconSizes.$normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.large {
|
.large {
|
||||||
width: IconSizes.$large;
|
min-width: IconSizes.$large;
|
||||||
height: IconSizes.$large;
|
min-height: IconSizes.$large;
|
||||||
|
max-width: IconSizes.$large;
|
||||||
|
max-height: IconSizes.$large;
|
||||||
}
|
}
|
||||||
|
|
||||||
.big {
|
.big {
|
||||||
width: IconSizes.$big;
|
min-width: IconSizes.$big;
|
||||||
height: IconSizes.$big;
|
min-height: IconSizes.$big;
|
||||||
|
max-width: IconSizes.$big;
|
||||||
|
max-height: IconSizes.$big;
|
||||||
}
|
}
|
||||||
|
|
||||||
.black {
|
.black {
|
||||||
|
49
frontend/lib/components/ui/Modal/Modal.module.scss
Normal file
49
frontend/lib/components/ui/Modal/Modal.module.scss
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
@use "@/styles/Colors.module.scss";
|
||||||
|
@use "@/styles/Radius.module.scss";
|
||||||
|
@use "@/styles/ScreenSizes.module.scss";
|
||||||
|
@use "@/styles/Spacings.module.scss";
|
||||||
|
@use "@/styles/ZIndexes.module.scss";
|
||||||
|
|
||||||
|
.modal_container {
|
||||||
|
display: flex;
|
||||||
|
background-color: rgba(Colors.$dark-black, 0.94);
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: ZIndexes.$modal;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.modal_content_wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: Radius.$big;
|
||||||
|
background-color: Colors.$white;
|
||||||
|
padding: Spacings.$spacing05;
|
||||||
|
cursor: auto;
|
||||||
|
box-shadow: 0 2px 4px rgb(0, 0, 0, 0.25);
|
||||||
|
max-width: 90vw;
|
||||||
|
overflow: scroll;
|
||||||
|
|
||||||
|
&.big_modal {
|
||||||
|
width: 50vw;
|
||||||
|
height: 90vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: ScreenSizes.$small) {
|
||||||
|
&.big_modal {
|
||||||
|
width: 90vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.close_button_wrapper {
|
||||||
|
display: inline-flex;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: Spacings.$spacing05;
|
||||||
|
border-radius: 50;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,9 @@ import { ReactNode, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { MdClose } from "react-icons/md";
|
import { MdClose } from "react-icons/md";
|
||||||
|
|
||||||
import Button from "./Button";
|
import styles from "./Modal.module.scss";
|
||||||
|
|
||||||
|
import Button from "../Button";
|
||||||
|
|
||||||
type CommonModalProps = {
|
type CommonModalProps = {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -17,6 +19,7 @@ type CommonModalProps = {
|
|||||||
CloseTrigger?: ReactNode;
|
CloseTrigger?: ReactNode;
|
||||||
isOpen?: undefined;
|
isOpen?: undefined;
|
||||||
setOpen?: undefined;
|
setOpen?: undefined;
|
||||||
|
bigModal?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ModalProps =
|
type ModalProps =
|
||||||
@ -34,6 +37,7 @@ export const Modal = ({
|
|||||||
CloseTrigger,
|
CloseTrigger,
|
||||||
isOpen: customIsOpen,
|
isOpen: customIsOpen,
|
||||||
setOpen: customSetOpen,
|
setOpen: customSetOpen,
|
||||||
|
bigModal,
|
||||||
}: ModalProps): JSX.Element => {
|
}: ModalProps): JSX.Element => {
|
||||||
const [isOpen, setOpen] = useState(false);
|
const [isOpen, setOpen] = useState(false);
|
||||||
const { t } = useTranslation(["translation"]);
|
const { t } = useTranslation(["translation"]);
|
||||||
@ -51,17 +55,19 @@ export const Modal = ({
|
|||||||
<Dialog.Portal forceMount>
|
<Dialog.Portal forceMount>
|
||||||
<Dialog.Overlay asChild forceMount>
|
<Dialog.Overlay asChild forceMount>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="z-[10000] py-20 fixed inset-0 flex justify-center overflow-auto cursor-pointer bg-black/50 backdrop-blur-sm"
|
className={styles.modal_container}
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
>
|
>
|
||||||
<Dialog.Content asChild forceMount>
|
<Dialog.Content asChild forceMount>
|
||||||
<motion.div
|
<motion.div
|
||||||
|
className={`${styles.modal_content_wrapper} ${
|
||||||
|
bigModal ? styles.big_modal : ""
|
||||||
|
}`}
|
||||||
initial={{ opacity: 0, y: "-40%" }}
|
initial={{ opacity: 0, y: "-40%" }}
|
||||||
animate={{ opacity: 1, y: "0%" }}
|
animate={{ opacity: 1, y: "0%" }}
|
||||||
exit={{ opacity: 0, y: "40%" }}
|
exit={{ opacity: 0, y: "40%" }}
|
||||||
className="w-[90vw] my-auto flex flex-col h-fit max-w-2xl rounded-xl bg-white dark:bg-black border border-black/10 dark:border-white/25 p-10 shadow-xl dark:shadow-primary/50 focus:outline-none cursor-auto"
|
|
||||||
>
|
>
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
className="m-0 text-2xl font-bold"
|
className="m-0 text-2xl font-bold"
|
||||||
@ -87,7 +93,7 @@ export const Modal = ({
|
|||||||
</Dialog.Close>
|
</Dialog.Close>
|
||||||
<Dialog.Close asChild>
|
<Dialog.Close asChild>
|
||||||
<button
|
<button
|
||||||
className="absolute top-0 p-5 right-0 inline-flex appearance-none items-center justify-center rounded-full focus:shadow-sm focus:outline-none"
|
className={styles.close_button_wrapper}
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
>
|
>
|
||||||
<MdClose />
|
<MdClose />
|
@ -13,6 +13,7 @@
|
|||||||
border: 1.5px solid transparent;
|
border: 1.5px solid transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
&.primary {
|
&.primary {
|
||||||
border-color: Colors.$primary;
|
border-color: Colors.$primary;
|
||||||
@ -33,6 +34,12 @@
|
|||||||
color: Colors.$white;
|
color: Colors.$white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
border-color: Colors.$normal-grey;
|
||||||
|
pointer-events: none;
|
||||||
|
color: Colors.$normal-grey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon_label {
|
.icon_label {
|
||||||
|
@ -13,29 +13,38 @@ export const QuivrButton = ({
|
|||||||
color,
|
color,
|
||||||
isLoading,
|
isLoading,
|
||||||
iconName,
|
iconName,
|
||||||
|
disabled,
|
||||||
}: ButtonType): JSX.Element => {
|
}: ButtonType): JSX.Element => {
|
||||||
const [hovered, setHovered] = useState<boolean>(false);
|
const [hovered, setHovered] = useState<boolean>(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`${styles.button_wrapper} ${styles[color]}`}
|
className={`
|
||||||
onClick={onClick}
|
${styles.button_wrapper}
|
||||||
|
${styles[color]}
|
||||||
|
${disabled ? styles.disabled : ""}
|
||||||
|
`}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
onClick={() => onClick()}
|
||||||
onMouseEnter={() => setHovered(true)}
|
onMouseEnter={() => setHovered(true)}
|
||||||
onMouseLeave={() => setHovered(false)}
|
onMouseLeave={() => setHovered(false)}
|
||||||
>
|
>
|
||||||
{!isLoading ? (
|
<div className={styles.icon_label}>
|
||||||
<div className={styles.icon_label}>
|
{!isLoading ? (
|
||||||
<Icon
|
<Icon
|
||||||
name={iconName}
|
name={iconName}
|
||||||
size="normal"
|
size="normal"
|
||||||
color={hovered ? "white" : color}
|
color={hovered ? "white" : disabled ? "grey" : color}
|
||||||
handleHover={false}
|
handleHover={false}
|
||||||
/>
|
/>
|
||||||
<span className={styles.label}>{label}</span>
|
) : (
|
||||||
</div>
|
<LoaderIcon
|
||||||
) : (
|
color={hovered ? "white" : disabled ? "grey" : color}
|
||||||
<LoaderIcon color="black" size="small" />
|
size="small"
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
|
<span className={styles.label}>{label}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,114 @@
|
|||||||
|
@use "@/styles/Colors.module.scss";
|
||||||
|
@use "@/styles/IconSizes.module.scss";
|
||||||
|
@use "@/styles/Radius.module.scss";
|
||||||
|
@use "@/styles/Spacings.module.scss";
|
||||||
|
@use "@/styles/Typography.module.scss";
|
||||||
|
|
||||||
|
.single_selector_wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.first_line_wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
border: 1px solid Colors.$normal-grey;
|
||||||
|
border-radius: Radius.$normal;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.unfolded {
|
||||||
|
border-radius: Radius.$normal Radius.$normal 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: Colors.$lightest-grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: Spacings.$spacing03;
|
||||||
|
padding: Spacings.$spacing03;
|
||||||
|
overflow: hidden;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: IconSizes.$normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
@include Typography.EllipsisOverflow;
|
||||||
|
color: Colors.$white;
|
||||||
|
background-color: Colors.$primary;
|
||||||
|
border-radius: Radius.$normal;
|
||||||
|
padding-inline: Spacings.$spacing05;
|
||||||
|
padding-block: Spacings.$spacing02;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&.not_set {
|
||||||
|
color: Colors.$normal-grey;
|
||||||
|
background-color: transparent;
|
||||||
|
padding-inline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unfolded_not_set {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 50%;
|
||||||
|
|
||||||
|
&.folded {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
position: absolute;
|
||||||
|
background-color: Colors.$white;
|
||||||
|
width: 100%;
|
||||||
|
top: 100%;
|
||||||
|
border: 1px solid Colors.$normal-grey;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 Radius.$normal Radius.$normal;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 180px;
|
||||||
|
overflow: scroll;
|
||||||
|
|
||||||
|
.option {
|
||||||
|
padding: Spacings.$spacing03;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
gap: Spacings.$spacing03;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: Colors.$lightest-grey;
|
||||||
|
|
||||||
|
.brain_name {
|
||||||
|
background-color: Colors.$primary;
|
||||||
|
color: Colors.$white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: IconSizes.$normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brain_name {
|
||||||
|
@include Typography.EllipsisOverflow;
|
||||||
|
border: 1px solid Colors.$lightest-black;
|
||||||
|
border-radius: Radius.$small;
|
||||||
|
padding-inline: Spacings.$spacing05;
|
||||||
|
padding-block: Spacings.$spacing02;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
92
frontend/lib/components/ui/SingleSelector/SingleSelector.tsx
Normal file
92
frontend/lib/components/ui/SingleSelector/SingleSelector.tsx
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { UUID } from "crypto";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import styles from "./SingleSelector.module.scss";
|
||||||
|
|
||||||
|
import { Icon } from "../Icon/Icon";
|
||||||
|
import { TextInput } from "../TextInput/TextInput";
|
||||||
|
|
||||||
|
export type SelectOptionProps<T> = {
|
||||||
|
label: string;
|
||||||
|
value: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SelectProps<T> = {
|
||||||
|
options: SelectOptionProps<T>[];
|
||||||
|
onChange: (option: T) => void;
|
||||||
|
selectedOption: SelectOptionProps<T> | undefined;
|
||||||
|
placeholder: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SingleSelector = <T extends string | number | UUID>({
|
||||||
|
onChange,
|
||||||
|
options,
|
||||||
|
selectedOption,
|
||||||
|
placeholder,
|
||||||
|
}: SelectProps<T>): JSX.Element => {
|
||||||
|
const [search, setSearch] = useState<string>("");
|
||||||
|
const [folded, setFolded] = useState<boolean>(true);
|
||||||
|
|
||||||
|
const filteredOptions = options.filter((option) =>
|
||||||
|
option.label.toLowerCase().includes(search.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOptionClick = (option: SelectOptionProps<T>) => {
|
||||||
|
onChange(option.value);
|
||||||
|
setFolded(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.single_selector_wrapper}>
|
||||||
|
<div
|
||||||
|
className={`${styles.first_line_wrapper} ${
|
||||||
|
!folded ? styles.unfolded : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className={styles.left} onClick={() => setFolded(!folded)}>
|
||||||
|
<div className={styles.icon}>
|
||||||
|
<Icon
|
||||||
|
name={folded ? "chevronDown" : "chevronRight"}
|
||||||
|
size="normal"
|
||||||
|
color="black"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
${styles.label}
|
||||||
|
${!selectedOption ? styles.not_set : ""}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{selectedOption?.label ?? placeholder}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!folded && (
|
||||||
|
<div className={styles.right}>
|
||||||
|
<TextInput
|
||||||
|
label="Search..."
|
||||||
|
inputValue={search}
|
||||||
|
setInputValue={setSearch}
|
||||||
|
simple={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!folded && (
|
||||||
|
<div className={styles.options}>
|
||||||
|
{filteredOptions.map((option) => (
|
||||||
|
<div
|
||||||
|
className={styles.option}
|
||||||
|
key={option.value.toString()}
|
||||||
|
onClick={() => handleOptionClick(option)}
|
||||||
|
>
|
||||||
|
<div className={styles.icon}>
|
||||||
|
<Icon name="brain" size="normal" color="black" />
|
||||||
|
</div>
|
||||||
|
<span className={styles.brain_name}>{option.label}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -7,13 +7,25 @@
|
|||||||
border: 1px solid Colors.$lighter-grey;
|
border: 1px solid Colors.$lighter-grey;
|
||||||
gap: Spacings.$spacing03;
|
gap: Spacings.$spacing03;
|
||||||
padding-block: Spacings.$spacing02;
|
padding-block: Spacings.$spacing02;
|
||||||
padding-inline: Spacings.$spacing05;
|
padding-inline: Spacings.$spacing03;
|
||||||
border-radius: Radius.$big;
|
border-radius: Radius.$big;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&.simple {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.text_input {
|
||||||
|
background-color: transparent;
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.text_input {
|
.text_input {
|
||||||
caret-color: Colors.$accent;
|
caret-color: Colors.$accent;
|
||||||
border: none;
|
border: none;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
@ -3,10 +3,12 @@ import styles from "./TextInput.module.scss";
|
|||||||
import { Icon } from "../Icon/Icon";
|
import { Icon } from "../Icon/Icon";
|
||||||
|
|
||||||
type TextInputProps = {
|
type TextInputProps = {
|
||||||
iconName: string;
|
iconName?: string;
|
||||||
label: string;
|
label: string;
|
||||||
inputValue: string;
|
inputValue: string;
|
||||||
setInputValue: (value: string) => void;
|
setInputValue: (value: string) => void;
|
||||||
|
simple?: boolean;
|
||||||
|
onSubmit?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TextInput = ({
|
export const TextInput = ({
|
||||||
@ -14,17 +16,36 @@ export const TextInput = ({
|
|||||||
label,
|
label,
|
||||||
inputValue,
|
inputValue,
|
||||||
setInputValue,
|
setInputValue,
|
||||||
|
simple,
|
||||||
|
onSubmit,
|
||||||
}: TextInputProps): JSX.Element => {
|
}: TextInputProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.text_input_container}>
|
<div
|
||||||
|
className={`
|
||||||
|
${styles.text_input_container}
|
||||||
|
${simple ? styles.simple : ""}
|
||||||
|
`}
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
className={styles.text_input}
|
className={styles.text_input}
|
||||||
type="text"
|
type="text"
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
placeholder={label}
|
placeholder={label}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter" && onSubmit) {
|
||||||
|
onSubmit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Icon name={iconName} size="small" color="black" />
|
{!simple && iconName && (
|
||||||
|
<Icon
|
||||||
|
name={iconName}
|
||||||
|
size="normal"
|
||||||
|
color={onSubmit ? (inputValue ? "accent" : "grey") : "black"}
|
||||||
|
onClick={onSubmit}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
FaRegUserCircle,
|
FaRegUserCircle,
|
||||||
FaUnlock,
|
FaUnlock,
|
||||||
} from "react-icons/fa";
|
} from "react-icons/fa";
|
||||||
|
import { FiUpload } from "react-icons/fi";
|
||||||
import { IoIosAdd, IoMdClose, IoMdLogOut } from "react-icons/io";
|
import { IoIosAdd, IoMdClose, IoMdLogOut } from "react-icons/io";
|
||||||
import {
|
import {
|
||||||
IoArrowUpCircleOutline,
|
IoArrowUpCircleOutline,
|
||||||
@ -34,6 +35,7 @@ import {
|
|||||||
MdUploadFile,
|
MdUploadFile,
|
||||||
} from "react-icons/md";
|
} from "react-icons/md";
|
||||||
import { RiHashtag } from "react-icons/ri";
|
import { RiHashtag } from "react-icons/ri";
|
||||||
|
import { TbNetwork } from "react-icons/tb";
|
||||||
import { VscGraph } from "react-icons/vsc";
|
import { VscGraph } from "react-icons/vsc";
|
||||||
|
|
||||||
export const iconList: { [name: string]: IconType } = {
|
export const iconList: { [name: string]: IconType } = {
|
||||||
@ -66,6 +68,8 @@ export const iconList: { [name: string]: IconType } = {
|
|||||||
settings: IoSettingsSharp,
|
settings: IoSettingsSharp,
|
||||||
star: FaRegStar,
|
star: FaRegStar,
|
||||||
unlock: FaUnlock,
|
unlock: FaUnlock,
|
||||||
upload: MdUploadFile,
|
upload: FiUpload,
|
||||||
|
uploadFile: MdUploadFile,
|
||||||
user: FaRegUserCircle,
|
user: FaRegUserCircle,
|
||||||
|
website: TbNetwork,
|
||||||
};
|
};
|
||||||
|
@ -7,5 +7,6 @@ export interface ButtonType {
|
|||||||
color: Color;
|
color: Color;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
iconName: keyof typeof iconList;
|
iconName: keyof typeof iconList;
|
||||||
onClick: () => void;
|
onClick: () => void | Promise<void>;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -1 +1,4 @@
|
|||||||
{}
|
{
|
||||||
|
"addKnowledgeTitle": "Add Knowledge",
|
||||||
|
"addKnowledgeSubtitle": "Feed your brain with knowledges from documents or websites"
|
||||||
|
}
|
||||||
|
@ -1 +1,4 @@
|
|||||||
{}
|
{
|
||||||
|
"addKnowledgeTitle": "Agregar Conocimiento",
|
||||||
|
"addKnowledgeSubtitle": "Alimenta tu cerebro con conocimientos de documentos o sitios web"
|
||||||
|
}
|
||||||
|
@ -1 +1,4 @@
|
|||||||
{}
|
{
|
||||||
|
"addKnowledgeTitle": "Ajouter de la connaissance",
|
||||||
|
"addKnowledgeSubtitle": "Nourris ton cerveau avec de la connaissance venant de documents ou de sites internets"
|
||||||
|
}
|
||||||
|
@ -1 +1,4 @@
|
|||||||
{}
|
{
|
||||||
|
"addKnowledgeTitle": "Adicionar Conhecimento",
|
||||||
|
"addKnowledgeSubtitle": "Alimente seu cérebro com conhecimento de documentos ou sites da internet"
|
||||||
|
}
|
||||||
|
@ -1 +1,4 @@
|
|||||||
{}
|
{
|
||||||
|
"addKnowledgeTitle": "Добавить Знание",
|
||||||
|
"addKnowledgeSubtitle": "Пополните свой мозг знаниями из документов или веб-сайтов"
|
||||||
|
}
|
||||||
|
@ -1 +1,4 @@
|
|||||||
{}
|
{
|
||||||
|
"addKnowledgeTitle": "添加知识",
|
||||||
|
"addKnowledgeSubtitle": "用文件或网站的知识充实你的大脑"
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user