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 { 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 { useFeedBrainHandler } from "./useFeedBrainHandler";
|
import { useFeedBrainHandler } from "./useFeedBrainHandler";
|
||||||
|
|
||||||
@ -18,14 +18,14 @@ export const useFeedBrain = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { publish } = useToast();
|
const { publish } = useToast();
|
||||||
const { t } = useTranslation(["upload"]);
|
const { t } = useTranslation(["upload"]);
|
||||||
const { currentBrainId } = useBrainContext();
|
const { brainId } = useUrlBrain();
|
||||||
const { setKnowledgeToFeed, knowledgeToFeed } = useKnowledgeToFeedContext();
|
const { setKnowledgeToFeed, knowledgeToFeed } = 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> => {
|
||||||
if (currentBrainId === null) {
|
if (brainId === undefined) {
|
||||||
publish({
|
publish({
|
||||||
variant: "danger",
|
variant: "danger",
|
||||||
text: t("selectBrainFirst"),
|
text: t("selectBrainFirst"),
|
||||||
@ -51,7 +51,7 @@ export const useFeedBrain = ({
|
|||||||
closeFeedInput?.();
|
closeFeedInput?.();
|
||||||
setHasPendingRequests(true);
|
setHasPendingRequests(true);
|
||||||
await handleFeedBrain({
|
await handleFeedBrain({
|
||||||
brainId: currentBrainId,
|
brainId,
|
||||||
chatId: currentChatId,
|
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 { AiOutlineLoading3Quarters } from "react-icons/ai";
|
||||||
import { MdDelete } from "react-icons/md";
|
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 { Knowledge } from "@/lib/types/Knowledge";
|
||||||
|
|
||||||
import { useKnowledgeItem } from "./useKnowledgeItem";
|
import { useKnowledgeItem } from "../hooks/useKnowledgeItem";
|
||||||
|
|
||||||
export const DeleteKnowledge = ({
|
export const DeleteKnowledge = ({
|
||||||
knowledge,
|
knowledge,
|
||||||
}: {
|
}: {
|
||||||
knowledge: Knowledge;
|
knowledge: Knowledge;
|
||||||
}): JSX.Element => {
|
}): JSX.Element => {
|
||||||
const { isDeleting, onDeleteKnowledge } = useKnowledgeItem();
|
const { isDeleting, onDeleteKnowledge } = useKnowledgeItem();
|
||||||
|
const { brain } = useUrlBrain();
|
||||||
|
|
||||||
const { currentBrain } = useBrainContext();
|
const canDeleteFile = brain?.role === "Owner";
|
||||||
|
|
||||||
const canDeleteFile = currentBrain?.role === "Owner";
|
|
||||||
|
|
||||||
if (!canDeleteFile) {
|
if (!canDeleteFile) {
|
||||||
return <></>;
|
return <></>;
|
||||||
@ -25,10 +25,7 @@ export const DeleteKnowledge = ({
|
|||||||
return isDeleting ? (
|
return isDeleting ? (
|
||||||
<AiOutlineLoading3Quarters />
|
<AiOutlineLoading3Quarters />
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button onClick={() => void onDeleteKnowledge(knowledge)}>
|
||||||
className="text-red-600 hover:text-red-900"
|
|
||||||
onClick={() => void onDeleteKnowledge(knowledge)}
|
|
||||||
>
|
|
||||||
<MdDelete size="20" />
|
<MdDelete size="20" />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
@ -1,17 +1,20 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { BsFillCloudArrowDownFill } from "react-icons/bs";
|
import { HiOutlineDownload } from "react-icons/hi";
|
||||||
|
|
||||||
import { useKnowledgeApi } from "@/lib/api/knowledge/useKnowledgeApi";
|
import { useKnowledgeApi } from "@/lib/api/knowledge/useKnowledgeApi";
|
||||||
import { getFileIcon } from "@/lib/helpers/getFileIcon";
|
import { isUploadedKnowledge, Knowledge } from "@/lib/types/Knowledge";
|
||||||
import { UploadedKnowledge } from "@/lib/types/Knowledge";
|
|
||||||
|
|
||||||
export const DownloadUploadedKnowledge = ({
|
export const DownloadUploadedKnowledge = ({
|
||||||
knowledge,
|
knowledge,
|
||||||
}: {
|
}: {
|
||||||
knowledge: UploadedKnowledge;
|
knowledge: Knowledge;
|
||||||
}): JSX.Element => {
|
}): JSX.Element => {
|
||||||
const { generateSignedUrlKnowledge } = useKnowledgeApi();
|
const { generateSignedUrlKnowledge } = useKnowledgeApi();
|
||||||
|
|
||||||
|
if (!isUploadedKnowledge(knowledge)) {
|
||||||
|
return <div />;
|
||||||
|
}
|
||||||
|
|
||||||
const downloadFile = async () => {
|
const downloadFile = async () => {
|
||||||
const download_url = await generateSignedUrlKnowledge({
|
const download_url = await generateSignedUrlKnowledge({
|
||||||
knowledgeId: knowledge.id,
|
knowledgeId: knowledge.id,
|
||||||
@ -37,12 +40,8 @@ export const DownloadUploadedKnowledge = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a onClick={() => void downloadFile()} className="cursor-pointer">
|
||||||
onClick={() => void downloadFile()}
|
<HiOutlineDownload fontSize="small" size={20} />
|
||||||
style={{ display: "flex", flexDirection: "column", alignItems: "center" }}
|
|
||||||
>
|
|
||||||
{getFileIcon(knowledge.fileName)}
|
|
||||||
<BsFillCloudArrowDownFill fontSize="small" />
|
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -3,12 +3,12 @@ import { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { useKnowledgeApi } from "@/lib/api/knowledge/useKnowledgeApi";
|
import { useKnowledgeApi } from "@/lib/api/knowledge/useKnowledgeApi";
|
||||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
|
||||||
import { useToast } from "@/lib/hooks";
|
import { useToast } from "@/lib/hooks";
|
||||||
|
import { useUrlBrain } from "@/lib/hooks/useBrainIdFromUrl";
|
||||||
import { Knowledge } from "@/lib/types/Knowledge";
|
import { Knowledge } from "@/lib/types/Knowledge";
|
||||||
import { useEventTracking } from "@/services/analytics/june/useEventTracking";
|
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
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
export const useKnowledgeItem = () => {
|
export const useKnowledgeItem = () => {
|
||||||
@ -16,10 +16,9 @@ export const useKnowledgeItem = () => {
|
|||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const { publish } = useToast();
|
const { publish } = useToast();
|
||||||
const { track } = useEventTracking();
|
const { track } = useEventTracking();
|
||||||
const { currentBrain } = useBrainContext();
|
const { brainId, brain } = useUrlBrain();
|
||||||
|
|
||||||
const { invalidateKnowledgeDataKey } = useKnowledge({
|
const { invalidateKnowledgeDataKey } = useKnowledge({
|
||||||
brainId: currentBrain?.id,
|
brainId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useTranslation(["translation", "explore"]);
|
const { t } = useTranslation(["translation", "explore"]);
|
||||||
@ -30,11 +29,11 @@ export const useKnowledgeItem = () => {
|
|||||||
const knowledge_name =
|
const knowledge_name =
|
||||||
"fileName" in knowledge ? knowledge.fileName : knowledge.url;
|
"fileName" in knowledge ? knowledge.fileName : knowledge.url;
|
||||||
try {
|
try {
|
||||||
if (currentBrain?.id === undefined) {
|
if (brainId === undefined) {
|
||||||
throw new Error(t("noBrain", { ns: "explore" }));
|
throw new Error(t("noBrain", { ns: "explore" }));
|
||||||
}
|
}
|
||||||
await deleteKnowledge({
|
await deleteKnowledge({
|
||||||
brainId: currentBrain.id,
|
brainId,
|
||||||
knowledgeId: knowledge.id,
|
knowledgeId: knowledge.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -44,7 +43,7 @@ export const useKnowledgeItem = () => {
|
|||||||
variant: "success",
|
variant: "success",
|
||||||
text: t("deleted", {
|
text: t("deleted", {
|
||||||
fileName: knowledge_name,
|
fileName: knowledge_name,
|
||||||
brain: currentBrain.name,
|
brain: brain?.name,
|
||||||
ns: "explore",
|
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";
|
"use client";
|
||||||
import { UUID } from "crypto";
|
import { UUID } from "crypto";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
|
||||||
import { useTranslation } from "react-i18next";
|
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 { KnowledgeToFeedProvider } from "@/lib/context";
|
||||||
|
|
||||||
import { AddKnowledge } from "./AddKnowledge";
|
import { AddKnowledge } from "./components/AddKnowledge/AddKnowledge";
|
||||||
import { KnowledgeTable } from "./KnowledgeTable";
|
import { AddedKnowledge } from "./components/AddedKnowledge/AddedKnowledge";
|
||||||
import { useKnowledge } from "./hooks/useKnowledge";
|
|
||||||
|
|
||||||
type KnowledgeTabProps = {
|
type KnowledgeTabProps = {
|
||||||
brainId: UUID;
|
brainId: UUID;
|
||||||
};
|
};
|
||||||
export const KnowledgeTab = ({ brainId }: KnowledgeTabProps): JSX.Element => {
|
export const KnowledgeTab = ({ brainId }: KnowledgeTabProps): JSX.Element => {
|
||||||
const { t } = useTranslation(["translation", "explore"]);
|
const { t } = useTranslation(["translation", "explore", "config"]);
|
||||||
const { isPending, allKnowledge } = useKnowledge({
|
|
||||||
brainId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KnowledgeToFeedProvider>
|
<KnowledgeToFeedProvider>
|
||||||
@ -29,25 +24,14 @@ export const KnowledgeTab = ({ brainId }: KnowledgeTabProps): JSX.Element => {
|
|||||||
</h1>
|
</h1>
|
||||||
<h2 className="opacity-50">{t("subtitle", { ns: "explore" })}</h2>
|
<h2 className="opacity-50">{t("subtitle", { ns: "explore" })}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
<Divider text={t("Upload")} />
|
||||||
<AddKnowledge />
|
<AddKnowledge />
|
||||||
{isPending ? (
|
<Divider
|
||||||
<Spinner />
|
text={t("knowledge", {
|
||||||
) : (
|
ns: "config",
|
||||||
<motion.div layout className="w-full max-w-xl flex flex-col gap-5">
|
})}
|
||||||
{allKnowledge.length !== 0 ? (
|
/>
|
||||||
<AnimatePresence mode="popLayout">
|
<AddedKnowledge brainId={brainId} />
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</KnowledgeToFeedProvider>
|
</KnowledgeToFeedProvider>
|
||||||
|
@ -40,7 +40,7 @@ export const GeneralInformation = (
|
|||||||
|
|
||||||
return (
|
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>
|
<div>
|
||||||
<Field
|
<Field
|
||||||
label={t("brainName", { ns: "brain" })}
|
label={t("brainName", { ns: "brain" })}
|
||||||
@ -54,29 +54,30 @@ export const GeneralInformation = (
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<div className="flex flex-1 items-center flex-col">
|
<div className="flex flex-1 items-end flex-col">
|
||||||
{isPublicBrain && !isOwnedByCurrentUser && (
|
{isPublicBrain && !isOwnedByCurrentUser && (
|
||||||
<Chip className="mb-3 bg-primary text-white w-full">
|
<Chip className="mb-3 bg-primary text-white w-full">
|
||||||
{t("brain:public_brain_label")}
|
{t("brain:public_brain_label")}
|
||||||
</Chip>
|
</Chip>
|
||||||
)}
|
)}
|
||||||
|
<div>
|
||||||
{isDefaultBrain ? (
|
{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">
|
<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" })}
|
{t("defaultBrain", { ns: "brain" })}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
hasEditRights && (
|
hasEditRights && (
|
||||||
<Button
|
<Button
|
||||||
variant={"secondary"}
|
variant={"secondary"}
|
||||||
isLoading={isSettingAsDefault}
|
isLoading={isSettingAsDefault}
|
||||||
onClick={() => void setAsDefaultBrainHandler()}
|
onClick={() => void setAsDefaultBrainHandler()}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{t("setDefaultBrain", { ns: "brain" })}
|
{t("setDefaultBrain", { ns: "brain" })}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,6 +43,7 @@ export const useBrainManagementTabs = () => {
|
|||||||
const params = useParams();
|
const params = useParams();
|
||||||
const { t } = useTranslation(["delete_or_unsubscribe_from_brain"]);
|
const { t } = useTranslation(["delete_or_unsubscribe_from_brain"]);
|
||||||
const brainId = params?.brainId as UUID | undefined;
|
const brainId = params?.brainId as UUID | undefined;
|
||||||
|
|
||||||
const { hasEditRights, isOwnedByCurrentUser } = getBrainPermissions({
|
const { hasEditRights, isOwnedByCurrentUser } = getBrainPermissions({
|
||||||
brainId,
|
brainId,
|
||||||
userAccessibleBrains: allBrains,
|
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