mirror of
https://github.com/StanGirard/quivr.git
synced 2024-12-25 12:22:58 +03:00
feat(brainSettings): rework knowledge tab (#1534)
Issue: https://github.com/StanGirard/quivr/issues/1435 - feat(knowledgeTab): update structure - refactor: change AddKnowledge structure - feat: change AddKnowledge component structure - feat: rework sources logic - feat: change knowledge tab upload process - fix: change knowledge tab fetch, create, update logic - feat: improve added knowledge ui - style: improve responsivity Fix: - https://github.com/StanGirard/quivr/issues/1516 - https://github.com/StanGirard/quivr/issues/1336 - https://github.com/StanGirard/quivr/issues/1204 https://github.com/StanGirard/quivr/assets/63923024/f2917bf3-4ff8-42c6-8149-0b36287441b4
This commit is contained in:
parent
b94642670c
commit
e3925bcbc0
@ -1,52 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { AiOutlineLoading3Quarters } from "react-icons/ai";
|
||||
|
||||
import { KnowledgeToFeedInput } from "@/lib/components/KnowledgeToFeedInput";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
|
||||
import { useFeedBrain } from "./hooks/useFeedBrain";
|
||||
import { useKnowledge } from "./hooks/useKnowledge";
|
||||
|
||||
export const AddKnowledge = (): JSX.Element => {
|
||||
const [shouldDisplayModal, setShouldDisplayModal] = useState(false);
|
||||
const { currentBrain } = useBrainContext();
|
||||
const { invalidateKnowledgeDataKey } = useKnowledge({
|
||||
brainId: currentBrain?.id,
|
||||
});
|
||||
|
||||
const { feedBrain, hasPendingRequests, setHasPendingRequests } = useFeedBrain(
|
||||
{
|
||||
dispatchHasPendingRequests: () => setHasPendingRequests(true),
|
||||
closeFeedInput: () => setShouldDisplayModal(false),
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasPendingRequests) {
|
||||
invalidateKnowledgeDataKey();
|
||||
}
|
||||
}, [hasPendingRequests, invalidateKnowledgeDataKey]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className="flex flex-1 items-center justify-center rounded-xl bg-white dark:bg-black border border-black/10 dark:border-white/25 p-2 md:p-6"
|
||||
onClick={() => setShouldDisplayModal(true)}
|
||||
>
|
||||
<div className="flex flex-lin items-center">
|
||||
<span className="text-2xl md:text-3xl">+</span>
|
||||
</div>
|
||||
</button>
|
||||
{hasPendingRequests && (
|
||||
<div className="flex mt-1 flex-col md:flex-row shadow-md dark:shadow-primary/25 hover:shadow-xl transition-shadow rounded-xl bg-white dark:bg-black border border-black/10 dark:border-white/25 p-2 md:p-6 pl-6">
|
||||
<AiOutlineLoading3Quarters className="animate-spin text-2xl md:text-3xl self-center" />
|
||||
</div>
|
||||
)}
|
||||
{shouldDisplayModal && (
|
||||
<KnowledgeToFeedInput feedBrain={() => void feedBrain()} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,31 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { MdLink } from "react-icons/md";
|
||||
|
||||
import { CrawledKnowledge } from "@/lib/types/Knowledge";
|
||||
|
||||
import { DeleteKnowledge } from "./DeleteKnowledge";
|
||||
|
||||
export const CrawledKnowledgeItem = ({
|
||||
knowledge,
|
||||
}: {
|
||||
knowledge: CrawledKnowledge;
|
||||
}): JSX.Element => {
|
||||
return (
|
||||
<tr key={knowledge.id}>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<a href={knowledge.url} target="_blank" rel="noopener noreferrer">
|
||||
<MdLink size="20" color="gray" />
|
||||
</a>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="text-sm text-gray-900">
|
||||
<p className={"max-w-[400px] truncate"}>{knowledge.url}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<DeleteKnowledge knowledge={knowledge} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
@ -1,28 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { UploadedKnowledge } from "@/lib/types/Knowledge";
|
||||
|
||||
import { DeleteKnowledge } from "./DeleteKnowledge";
|
||||
import { DownloadUploadedKnowledge } from "./DownloadUploadedKnowledge";
|
||||
|
||||
export const UploadedKnowledgeItem = ({
|
||||
knowledge,
|
||||
}: {
|
||||
knowledge: UploadedKnowledge;
|
||||
}): JSX.Element => {
|
||||
return (
|
||||
<tr key={knowledge.id}>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<DownloadUploadedKnowledge knowledge={knowledge} />
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="text-sm text-gray-900">
|
||||
<p className={"max-w-[400px] truncate"}>{knowledge.fileName}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<DeleteKnowledge knowledge={knowledge} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
@ -1,21 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { isUploadedKnowledge, Knowledge } from "@/lib/types/Knowledge";
|
||||
|
||||
import { CrawledKnowledgeItem } from "./CrawledKnowledgeItem";
|
||||
import { UploadedKnowledgeItem } from "./UploadedKnowledgeItem";
|
||||
|
||||
const KnowledgeItem = ({
|
||||
knowledge,
|
||||
}: {
|
||||
knowledge: Knowledge;
|
||||
}): JSX.Element => {
|
||||
return isUploadedKnowledge(knowledge) ? (
|
||||
<UploadedKnowledgeItem knowledge={knowledge} />
|
||||
) : (
|
||||
<CrawledKnowledgeItem knowledge={knowledge} />
|
||||
);
|
||||
};
|
||||
|
||||
KnowledgeItem.displayName = "KnowledgeItem";
|
||||
export default KnowledgeItem;
|
@ -1,23 +0,0 @@
|
||||
import { Knowledge } from "@/lib/types/Knowledge";
|
||||
|
||||
import KnowledgeItem from "./KnowledgeItem";
|
||||
|
||||
interface KnowledgeTableProps {
|
||||
knowledgeList: Knowledge[];
|
||||
}
|
||||
|
||||
export const KnowledgeTable = ({
|
||||
knowledgeList,
|
||||
}: KnowledgeTableProps): JSX.Element => {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{knowledgeList.map((knowledge) => (
|
||||
<KnowledgeItem knowledge={knowledge} key={knowledge.id} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import { AiOutlineLoading3Quarters } from "react-icons/ai";
|
||||
|
||||
import { KnowledgeToFeedInput } from "@/lib/components/KnowledgeToFeedInput";
|
||||
|
||||
import { useAddKnowledge } from "./hooks/useAddKnowledge";
|
||||
|
||||
export const AddKnowledge = (): JSX.Element => {
|
||||
const { hasPendingRequests, feedBrain } = useAddKnowledge();
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasPendingRequests && (
|
||||
<div className="flex mt-1 flex-col md:flex-row shadow-md dark:shadow-primary/25 hover:shadow-xl transition-shadow rounded-xl bg-white dark:bg-black border border-black/10 dark:border-white/25 p-2 md:p-6 pl-6">
|
||||
<AiOutlineLoading3Quarters className="animate-spin text-2xl md:text-3xl self-center" />
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full shadow-md dark:shadow-primary/25 rounded-xl bg-white dark:bg-black border border-black/10 dark:border-white/25 p-4 mt-0 py-10">
|
||||
<KnowledgeToFeedInput feedBrain={() => void feedBrain()} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
@ -0,0 +1,31 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { useUrlBrain } from "@/lib/hooks/useBrainIdFromUrl";
|
||||
|
||||
import { useFeedBrain } from "./useFeedBrain";
|
||||
import { useKnowledge } from "../../../hooks/useKnowledge";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useAddKnowledge = () => {
|
||||
const { brainId } = useUrlBrain();
|
||||
const { invalidateKnowledgeDataKey } = useKnowledge({
|
||||
brainId,
|
||||
});
|
||||
|
||||
const { feedBrain, hasPendingRequests, setHasPendingRequests } = useFeedBrain(
|
||||
{
|
||||
dispatchHasPendingRequests: () => setHasPendingRequests(true),
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasPendingRequests) {
|
||||
invalidateKnowledgeDataKey();
|
||||
}
|
||||
}, [hasPendingRequests, invalidateKnowledgeDataKey]);
|
||||
|
||||
return {
|
||||
feedBrain,
|
||||
hasPendingRequests,
|
||||
};
|
||||
};
|
@ -2,9 +2,9 @@ import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useChatApi } from "@/lib/api/chat/useChatApi";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
||||
import { useToast } from "@/lib/hooks";
|
||||
import { useUrlBrain } from "@/lib/hooks/useBrainIdFromUrl";
|
||||
|
||||
import { useFeedBrainHandler } from "./useFeedBrainHandler";
|
||||
|
||||
@ -18,14 +18,14 @@ export const useFeedBrain = ({
|
||||
}) => {
|
||||
const { publish } = useToast();
|
||||
const { t } = useTranslation(["upload"]);
|
||||
const { currentBrainId } = useBrainContext();
|
||||
const { brainId } = useUrlBrain();
|
||||
const { setKnowledgeToFeed, knowledgeToFeed } = useKnowledgeToFeedContext();
|
||||
const [hasPendingRequests, setHasPendingRequests] = useState(false);
|
||||
const { handleFeedBrain } = useFeedBrainHandler();
|
||||
const { createChat, deleteChat } = useChatApi();
|
||||
|
||||
const feedBrain = async (): Promise<void> => {
|
||||
if (currentBrainId === null) {
|
||||
if (brainId === undefined) {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: t("selectBrainFirst"),
|
||||
@ -51,7 +51,7 @@ export const useFeedBrain = ({
|
||||
closeFeedInput?.();
|
||||
setHasPendingRequests(true);
|
||||
await handleFeedBrain({
|
||||
brainId: currentBrainId,
|
||||
brainId,
|
||||
chatId: currentChatId,
|
||||
});
|
||||
|
@ -0,0 +1,46 @@
|
||||
import { UUID } from "crypto";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import Spinner from "@/lib/components/ui/Spinner";
|
||||
|
||||
import { useAddedKnowledge } from "./hooks/useAddedKnowledge";
|
||||
import { KnowledgeTable } from "../KnowledgeTable/KnowledgeTable";
|
||||
|
||||
type AddedKnowledgeProps = {
|
||||
brainId: UUID;
|
||||
};
|
||||
export const AddedKnowledge = ({
|
||||
brainId,
|
||||
}: AddedKnowledgeProps): JSX.Element => {
|
||||
const { isPending, allKnowledge } = useAddedKnowledge({
|
||||
brainId,
|
||||
});
|
||||
|
||||
const { t } = useTranslation("explore");
|
||||
|
||||
if (isPending) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
if (allKnowledge.length === 0) {
|
||||
return (
|
||||
<motion.div layout className="w-full max-w-xl flex flex-col gap-5">
|
||||
<div className="flex flex-col items-center justify-center mt-0 gap-1">
|
||||
<p className="text-center">{t("empty", { ns: "explore" })}</p>
|
||||
<p className="text-center">
|
||||
{t("feed_brain_instructions", { ns: "explore" })}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.div layout className="w-full flex flex-col gap-5">
|
||||
<AnimatePresence mode="popLayout">
|
||||
<KnowledgeTable knowledgeList={allKnowledge} />
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
@ -0,0 +1,44 @@
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { UUID } from "crypto";
|
||||
|
||||
import { getKnowledgeDataKey } from "@/lib/api/knowledge/config";
|
||||
import { useKnowledgeApi } from "@/lib/api/knowledge/useKnowledgeApi";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useAddedKnowledge = ({ brainId }: { brainId?: UUID }) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { getAllKnowledge } = useKnowledgeApi();
|
||||
|
||||
const fetchKnowledge = () => {
|
||||
if (brainId !== undefined) {
|
||||
return getAllKnowledge({ brainId });
|
||||
}
|
||||
};
|
||||
|
||||
const { data: allKnowledge, isLoading: isPending } = useQuery({
|
||||
queryKey: brainId !== undefined ? [getKnowledgeDataKey(brainId)] : [],
|
||||
queryFn: fetchKnowledge,
|
||||
enabled: brainId !== undefined,
|
||||
});
|
||||
|
||||
if (brainId === undefined) {
|
||||
return {
|
||||
invalidateKnowledgeDataKey: () => void {},
|
||||
isPending: false,
|
||||
allKnowledge: [],
|
||||
};
|
||||
}
|
||||
|
||||
const knowledge_data_key = getKnowledgeDataKey(brainId);
|
||||
|
||||
const invalidateKnowledgeDataKey = () => {
|
||||
void queryClient.invalidateQueries({ queryKey: [knowledge_data_key] });
|
||||
};
|
||||
|
||||
return {
|
||||
invalidateKnowledgeDataKey,
|
||||
isPending,
|
||||
allKnowledge: allKnowledge ?? [],
|
||||
};
|
||||
};
|
@ -0,0 +1,19 @@
|
||||
import { Knowledge } from "@/lib/types/Knowledge";
|
||||
|
||||
import KnowledgeItem from "./components/KnowledgeItem";
|
||||
|
||||
interface KnowledgeTableProps {
|
||||
knowledgeList: Knowledge[];
|
||||
}
|
||||
|
||||
export const KnowledgeTable = ({
|
||||
knowledgeList,
|
||||
}: KnowledgeTableProps): JSX.Element => {
|
||||
return (
|
||||
<div className="w-full shadow-md dark:shadow-primary/25 rounded-xl bg-white dark:bg-black border border-black/10 dark:border-white/25 mt-0 p-5">
|
||||
{knowledgeList.map((knowledge) => (
|
||||
<KnowledgeItem knowledge={knowledge} key={knowledge.id} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
export const CrawledKnowledgeItem = ({ url }: { url: string }): JSX.Element => {
|
||||
return (
|
||||
<a href={url} target="_blank" rel="noopener noreferrer">
|
||||
<div className="text-sm text-gray-900">
|
||||
<p className={"max-w-[400px] truncate"}>{url}</p>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
};
|
@ -3,20 +3,20 @@
|
||||
import { AiOutlineLoading3Quarters } from "react-icons/ai";
|
||||
import { MdDelete } from "react-icons/md";
|
||||
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
import { useUrlBrain } from "@/lib/hooks/useBrainIdFromUrl";
|
||||
import { Knowledge } from "@/lib/types/Knowledge";
|
||||
|
||||
import { useKnowledgeItem } from "./useKnowledgeItem";
|
||||
import { useKnowledgeItem } from "../hooks/useKnowledgeItem";
|
||||
|
||||
export const DeleteKnowledge = ({
|
||||
knowledge,
|
||||
}: {
|
||||
knowledge: Knowledge;
|
||||
}): JSX.Element => {
|
||||
const { isDeleting, onDeleteKnowledge } = useKnowledgeItem();
|
||||
const { brain } = useUrlBrain();
|
||||
|
||||
const { currentBrain } = useBrainContext();
|
||||
|
||||
const canDeleteFile = currentBrain?.role === "Owner";
|
||||
const canDeleteFile = brain?.role === "Owner";
|
||||
|
||||
if (!canDeleteFile) {
|
||||
return <></>;
|
||||
@ -25,10 +25,7 @@ export const DeleteKnowledge = ({
|
||||
return isDeleting ? (
|
||||
<AiOutlineLoading3Quarters />
|
||||
) : (
|
||||
<button
|
||||
className="text-red-600 hover:text-red-900"
|
||||
onClick={() => void onDeleteKnowledge(knowledge)}
|
||||
>
|
||||
<button onClick={() => void onDeleteKnowledge(knowledge)}>
|
||||
<MdDelete size="20" />
|
||||
</button>
|
||||
);
|
@ -1,17 +1,20 @@
|
||||
import axios from "axios";
|
||||
import { BsFillCloudArrowDownFill } from "react-icons/bs";
|
||||
import { HiOutlineDownload } from "react-icons/hi";
|
||||
|
||||
import { useKnowledgeApi } from "@/lib/api/knowledge/useKnowledgeApi";
|
||||
import { getFileIcon } from "@/lib/helpers/getFileIcon";
|
||||
import { UploadedKnowledge } from "@/lib/types/Knowledge";
|
||||
import { isUploadedKnowledge, Knowledge } from "@/lib/types/Knowledge";
|
||||
|
||||
export const DownloadUploadedKnowledge = ({
|
||||
knowledge,
|
||||
}: {
|
||||
knowledge: UploadedKnowledge;
|
||||
knowledge: Knowledge;
|
||||
}): JSX.Element => {
|
||||
const { generateSignedUrlKnowledge } = useKnowledgeApi();
|
||||
|
||||
if (!isUploadedKnowledge(knowledge)) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
const downloadFile = async () => {
|
||||
const download_url = await generateSignedUrlKnowledge({
|
||||
knowledgeId: knowledge.id,
|
||||
@ -37,12 +40,8 @@ export const DownloadUploadedKnowledge = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<a
|
||||
onClick={() => void downloadFile()}
|
||||
style={{ display: "flex", flexDirection: "column", alignItems: "center" }}
|
||||
>
|
||||
{getFileIcon(knowledge.fileName)}
|
||||
<BsFillCloudArrowDownFill fontSize="small" />
|
||||
<a onClick={() => void downloadFile()} className="cursor-pointer">
|
||||
<HiOutlineDownload fontSize="small" size={20} />
|
||||
</a>
|
||||
);
|
||||
};
|
@ -3,12 +3,12 @@ import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useKnowledgeApi } from "@/lib/api/knowledge/useKnowledgeApi";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
import { useToast } from "@/lib/hooks";
|
||||
import { useUrlBrain } from "@/lib/hooks/useBrainIdFromUrl";
|
||||
import { Knowledge } from "@/lib/types/Knowledge";
|
||||
import { useEventTracking } from "@/services/analytics/june/useEventTracking";
|
||||
|
||||
import { useKnowledge } from "../hooks/useKnowledge";
|
||||
import { useKnowledge } from "../../../../../hooks/useKnowledge";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useKnowledgeItem = () => {
|
||||
@ -16,10 +16,9 @@ export const useKnowledgeItem = () => {
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const { publish } = useToast();
|
||||
const { track } = useEventTracking();
|
||||
const { currentBrain } = useBrainContext();
|
||||
|
||||
const { brainId, brain } = useUrlBrain();
|
||||
const { invalidateKnowledgeDataKey } = useKnowledge({
|
||||
brainId: currentBrain?.id,
|
||||
brainId,
|
||||
});
|
||||
|
||||
const { t } = useTranslation(["translation", "explore"]);
|
||||
@ -30,11 +29,11 @@ export const useKnowledgeItem = () => {
|
||||
const knowledge_name =
|
||||
"fileName" in knowledge ? knowledge.fileName : knowledge.url;
|
||||
try {
|
||||
if (currentBrain?.id === undefined) {
|
||||
if (brainId === undefined) {
|
||||
throw new Error(t("noBrain", { ns: "explore" }));
|
||||
}
|
||||
await deleteKnowledge({
|
||||
brainId: currentBrain.id,
|
||||
brainId,
|
||||
knowledgeId: knowledge.id,
|
||||
});
|
||||
|
||||
@ -44,7 +43,7 @@ export const useKnowledgeItem = () => {
|
||||
variant: "success",
|
||||
text: t("deleted", {
|
||||
fileName: knowledge_name,
|
||||
brain: currentBrain.name,
|
||||
brain: brain?.name,
|
||||
ns: "explore",
|
||||
}),
|
||||
});
|
@ -0,0 +1,51 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { MdLink } from "react-icons/md";
|
||||
|
||||
import { getFileIcon } from "@/lib/helpers/getFileIcon";
|
||||
import { isUploadedKnowledge, Knowledge } from "@/lib/types/Knowledge";
|
||||
|
||||
import { CrawledKnowledgeItem } from "./components/CrawledKnowledgeItem";
|
||||
import { DeleteKnowledge } from "./components/DeleteKnowledge";
|
||||
import { DownloadUploadedKnowledge } from "./components/DownloadUploadedKnowledge";
|
||||
|
||||
const KnowledgeItem = ({
|
||||
knowledge,
|
||||
}: {
|
||||
knowledge: Knowledge;
|
||||
}): JSX.Element => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
className="hover:bg-gray-50 rounded-lg flex justify-between w-full py-2 px-1"
|
||||
>
|
||||
<div className="text-sm text-gray-900 flex gap-3 items-center">
|
||||
{isUploadedKnowledge(knowledge) ? (
|
||||
getFileIcon(knowledge.fileName)
|
||||
) : (
|
||||
<MdLink size="20" color="gray" />
|
||||
)}
|
||||
{isUploadedKnowledge(knowledge) ? (
|
||||
<p className={"max-w-[400px] truncate"}>{knowledge.fileName}</p>
|
||||
) : (
|
||||
<CrawledKnowledgeItem url={knowledge.url} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-end items-center">
|
||||
{isHovered && (
|
||||
<div className="flex items-center gap-2">
|
||||
<DownloadUploadedKnowledge knowledge={knowledge} />
|
||||
<DeleteKnowledge knowledge={knowledge} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
KnowledgeItem.displayName = "KnowledgeItem";
|
||||
export default KnowledgeItem;
|
@ -1,23 +1,18 @@
|
||||
"use client";
|
||||
import { UUID } from "crypto";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import Spinner from "@/lib/components/ui/Spinner";
|
||||
import { Divider } from "@/lib/components/ui/Divider";
|
||||
import { KnowledgeToFeedProvider } from "@/lib/context";
|
||||
|
||||
import { AddKnowledge } from "./AddKnowledge";
|
||||
import { KnowledgeTable } from "./KnowledgeTable";
|
||||
import { useKnowledge } from "./hooks/useKnowledge";
|
||||
import { AddKnowledge } from "./components/AddKnowledge/AddKnowledge";
|
||||
import { AddedKnowledge } from "./components/AddedKnowledge/AddedKnowledge";
|
||||
|
||||
type KnowledgeTabProps = {
|
||||
brainId: UUID;
|
||||
};
|
||||
export const KnowledgeTab = ({ brainId }: KnowledgeTabProps): JSX.Element => {
|
||||
const { t } = useTranslation(["translation", "explore"]);
|
||||
const { isPending, allKnowledge } = useKnowledge({
|
||||
brainId,
|
||||
});
|
||||
const { t } = useTranslation(["translation", "explore", "config"]);
|
||||
|
||||
return (
|
||||
<KnowledgeToFeedProvider>
|
||||
@ -29,25 +24,14 @@ export const KnowledgeTab = ({ brainId }: KnowledgeTabProps): JSX.Element => {
|
||||
</h1>
|
||||
<h2 className="opacity-50">{t("subtitle", { ns: "explore" })}</h2>
|
||||
</div>
|
||||
<Divider text={t("Upload")} />
|
||||
<AddKnowledge />
|
||||
{isPending ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<motion.div layout className="w-full max-w-xl flex flex-col gap-5">
|
||||
{allKnowledge.length !== 0 ? (
|
||||
<AnimatePresence mode="popLayout">
|
||||
<KnowledgeTable knowledgeList={allKnowledge} />
|
||||
</AnimatePresence>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center mt-10 gap-1">
|
||||
<p className="text-center">{t("empty", { ns: "explore" })}</p>
|
||||
<p className="text-center">
|
||||
{t("feed_brain_instructions", { ns: "explore" })}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
)}
|
||||
<Divider
|
||||
text={t("knowledge", {
|
||||
ns: "config",
|
||||
})}
|
||||
/>
|
||||
<AddedKnowledge brainId={brainId} />
|
||||
</section>
|
||||
</main>
|
||||
</KnowledgeToFeedProvider>
|
||||
|
@ -40,7 +40,7 @@ export const GeneralInformation = (
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row flex-1 justify-between w-full items-end">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 justify-between w-full items-end">
|
||||
<div>
|
||||
<Field
|
||||
label={t("brainName", { ns: "brain" })}
|
||||
@ -54,29 +54,30 @@ export const GeneralInformation = (
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<div className="flex flex-1 items-center flex-col">
|
||||
<div className="flex flex-1 items-end flex-col">
|
||||
{isPublicBrain && !isOwnedByCurrentUser && (
|
||||
<Chip className="mb-3 bg-primary 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>
|
||||
) : (
|
||||
hasEditRights && (
|
||||
<Button
|
||||
variant={"secondary"}
|
||||
isLoading={isSettingAsDefault}
|
||||
onClick={() => void setAsDefaultBrainHandler()}
|
||||
type="button"
|
||||
>
|
||||
{t("setDefaultBrain", { ns: "brain" })}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
<div>
|
||||
{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>
|
||||
) : (
|
||||
hasEditRights && (
|
||||
<Button
|
||||
variant={"secondary"}
|
||||
isLoading={isSettingAsDefault}
|
||||
onClick={() => void setAsDefaultBrainHandler()}
|
||||
type="button"
|
||||
>
|
||||
{t("setDefaultBrain", { ns: "brain" })}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -43,6 +43,7 @@ export const useBrainManagementTabs = () => {
|
||||
const params = useParams();
|
||||
const { t } = useTranslation(["delete_or_unsubscribe_from_brain"]);
|
||||
const brainId = params?.brainId as UUID | undefined;
|
||||
|
||||
const { hasEditRights, isOwnedByCurrentUser } = getBrainPermissions({
|
||||
brainId,
|
||||
userAccessibleBrains: allBrains,
|
||||
|
19
frontend/lib/hooks/useBrainIdFromUrl.ts
Normal file
19
frontend/lib/hooks/useBrainIdFromUrl.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { UUID } from "crypto";
|
||||
import { useParams } from "next/navigation";
|
||||
|
||||
import { useBrainContext } from "../context/BrainProvider/hooks/useBrainContext";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useUrlBrain = () => {
|
||||
const { allBrains } = useBrainContext();
|
||||
|
||||
const params = useParams();
|
||||
|
||||
const brainId = params?.brainId as UUID | undefined;
|
||||
const correspondingBrain = allBrains.find((brain) => brain.id === brainId);
|
||||
|
||||
return {
|
||||
brain: correspondingBrain,
|
||||
brainId,
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user