diff --git a/surfsense_web/atoms/documents/document-mutation.atoms.ts b/surfsense_web/atoms/documents/document-mutation.atoms.ts new file mode 100644 index 000000000..ec7bad237 --- /dev/null +++ b/surfsense_web/atoms/documents/document-mutation.atoms.ts @@ -0,0 +1,115 @@ +import { atomWithMutation } from "jotai-tanstack-query"; +import { toast } from "sonner"; +import { activeSearchSpaceIdAtom } from "@/atoms/seach-spaces/seach-space-queries.atom"; +import type { + CreateDocumentRequest, + DeleteDocumentRequest, + GetDocumentsResponse, + UpdateDocumentRequest, + UploadDocumentRequest, +} from "@/contracts/types/document.types"; +import { documentsApiService } from "@/lib/apis/documents-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; +import { queryClient } from "@/lib/query-client/client"; +import { globalDocumentsQueryParamsAtom } from "./ui.atoms"; + +export const createDocumentMutationAtom = atomWithMutation((get) => { + const searchSpaceId = get(activeSearchSpaceIdAtom); + const documentsQueryParams = get(globalDocumentsQueryParamsAtom); + + return { + mutationKey: cacheKeys.documents.globalQueryParams(documentsQueryParams), + enabled: !!searchSpaceId, + mutationFn: async (request: CreateDocumentRequest) => { + return documentsApiService.createDocument(request); + }, + + onSuccess: () => { + toast.success("Document created successfully"); + queryClient.invalidateQueries({ + queryKey: cacheKeys.documents.globalQueryParams(documentsQueryParams), + }); + queryClient.invalidateQueries({ + queryKey: cacheKeys.documents.typeCounts(searchSpaceId ?? undefined), + }); + }, + }; +}); + +export const uploadDocumentMutationAtom = atomWithMutation((get) => { + const searchSpaceId = get(activeSearchSpaceIdAtom); + const documentsQueryParams = get(globalDocumentsQueryParamsAtom); + + return { + mutationKey: cacheKeys.documents.globalQueryParams(documentsQueryParams), + enabled: !!searchSpaceId, + mutationFn: async (request: UploadDocumentRequest) => { + return documentsApiService.uploadDocument(request); + }, + + onSuccess: () => { + toast.success("Files uploaded for processing"); + }, + }; +}); + +export const updateDocumentMutationAtom = atomWithMutation((get) => { + const searchSpaceId = get(activeSearchSpaceIdAtom); + const documentsQueryParams = get(globalDocumentsQueryParamsAtom); + + return { + mutationKey: cacheKeys.documents.globalQueryParams(documentsQueryParams), + enabled: !!searchSpaceId, + mutationFn: async (request: UpdateDocumentRequest) => { + return documentsApiService.updateDocument(request); + }, + + onSuccess: (_, request: UpdateDocumentRequest) => { + toast.success("Document updated successfully"); + queryClient.invalidateQueries({ + queryKey: cacheKeys.documents.globalQueryParams(documentsQueryParams), + }); + queryClient.invalidateQueries({ + queryKey: cacheKeys.documents.document(String(request.id)), + }); + queryClient.invalidateQueries({ + queryKey: cacheKeys.documents.typeCounts(searchSpaceId ?? undefined), + }); + }, + }; +}); + +export const deleteDocumentMutationAtom = atomWithMutation((get) => { + const searchSpaceId = get(activeSearchSpaceIdAtom); + const authToken = localStorage.getItem("surfsense_bearer_token"); + const documentsQueryParams = get(globalDocumentsQueryParamsAtom); + + return { + mutationKey: cacheKeys.documents.globalQueryParams(documentsQueryParams), + enabled: !!searchSpaceId && !!authToken, + mutationFn: async (request: DeleteDocumentRequest) => { + return documentsApiService.deleteDocument(request); + }, + + onSuccess: (_, request: DeleteDocumentRequest) => { + toast.success("Document deleted successfully"); + queryClient.setQueryData( + cacheKeys.documents.globalQueryParams(documentsQueryParams), + (oldData: GetDocumentsResponse | undefined) => { + if (!oldData) return oldData; + return { + ...oldData, + items: oldData.items.filter((doc) => doc.id !== request.id), + total: oldData.total - 1, + }; + } + ); + queryClient.invalidateQueries({ + queryKey: cacheKeys.documents.document(String(request.id)), + }); + queryClient.invalidateQueries({ + queryKey: cacheKeys.documents.typeCounts(searchSpaceId ?? undefined), + }); + }, + }; +}); diff --git a/surfsense_web/atoms/documents/document-query.atoms.ts b/surfsense_web/atoms/documents/document-query.atoms.ts new file mode 100644 index 000000000..d1b5466a3 --- /dev/null +++ b/surfsense_web/atoms/documents/document-query.atoms.ts @@ -0,0 +1,86 @@ +import { atomWithQuery } from "jotai-tanstack-query"; +import { activeSearchSpaceIdAtom } from "@/atoms/seach-spaces/seach-space-queries.atom"; +import type { SearchDocumentsRequest } from "@/contracts/types/document.types"; +import { documentsApiService } from "@/lib/apis/documents-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; +import { globalDocumentsQueryParamsAtom } from "./ui.atoms"; + +export const documentsAtom = atomWithQuery((get) => { + const searchSpaceId = get(activeSearchSpaceIdAtom); + const queryParams = get(globalDocumentsQueryParamsAtom); + + return { + queryKey: cacheKeys.documents.globalQueryParams(queryParams), + enabled: !!searchSpaceId, + queryFn: async () => { + return documentsApiService.getDocuments({ + queryParams: queryParams, + }); + }, + }; +}); + +export const getDocumentAtom = (documentId: number) => + atomWithQuery(() => { + return { + queryKey: cacheKeys.documents.document(String(documentId)), + enabled: !!documentId, + queryFn: async () => { + if (!documentId) { + throw new Error("No active document id found"); + } + + return documentsApiService.getDocument({ id: documentId }); + }, + }; + }); + +export const documentTypeCountsAtom = atomWithQuery((get) => { + const searchSpaceId = get(activeSearchSpaceIdAtom); + + return { + queryKey: cacheKeys.documents.typeCounts(searchSpaceId ?? undefined), + enabled: !!searchSpaceId, + queryFn: async () => { + return documentsApiService.getDocumentTypeCounts({ + queryParams: { + search_space_id: searchSpaceId ?? undefined, + }, + }); + }, + }; +}); + +export const getDocumentByChunkAtom = (chunkId: number) => + atomWithQuery(() => { + return { + queryKey: cacheKeys.documents.byChunk(String(chunkId)), + enabled: !!chunkId, + queryFn: async () => { + if (!chunkId) { + throw new Error("No active chunk id found"); + } + + return documentsApiService.getDocumentByChunk({ chunk_id: chunkId }); + }, + }; + }); + +export const searchDocumentsAtom = (request: SearchDocumentsRequest) => + atomWithQuery((get) => { + const searchSpaceId = get(activeSearchSpaceIdAtom); + + return { + queryKey: cacheKeys.documents.globalQueryParams(request.queryParams), + enabled: !!searchSpaceId, + queryFn: async () => { + return documentsApiService.searchDocuments({ + ...request, + queryParams: { + ...request.queryParams, + search_space_id: searchSpaceId ?? undefined, + }, + }); + }, + }; + }); diff --git a/surfsense_web/atoms/documents/ui.atoms.ts b/surfsense_web/atoms/documents/ui.atoms.ts new file mode 100644 index 000000000..33740e9c7 --- /dev/null +++ b/surfsense_web/atoms/documents/ui.atoms.ts @@ -0,0 +1,7 @@ +import { atom } from "jotai"; +import type { GetDocumentsRequest } from "@/contracts/types/document.types"; + +export const globalDocumentsQueryParamsAtom = atom({ + page_size: 10, + page: 0, +}); diff --git a/surfsense_web/lib/query-client/cache-keys.ts b/surfsense_web/lib/query-client/cache-keys.ts index 12b73bc35..813ba62cf 100644 --- a/surfsense_web/lib/query-client/cache-keys.ts +++ b/surfsense_web/lib/query-client/cache-keys.ts @@ -1,4 +1,5 @@ import type { GetChatsRequest } from "@/contracts/types/chat.types"; +import type { GetDocumentsRequest } from "@/contracts/types/document.types"; import type { GetPodcastsRequest } from "@/contracts/types/podcast.types"; export const cacheKeys = { @@ -11,6 +12,13 @@ export const cacheKeys = { globalQueryParams: (queries: GetPodcastsRequest["queryParams"]) => ["podcasts", ...(queries ? Object.values(queries) : [])] as const, }, + documents: { + globalQueryParams: (queries: GetDocumentsRequest["queryParams"]) => + ["documents", ...(queries ? Object.values(queries) : [])] as const, + document: (documentId: string) => ["document", documentId] as const, + typeCounts: (searchSpaceId?: string) => ["documents", "type-counts", searchSpaceId] as const, + byChunk: (chunkId: string) => ["documents", "by-chunk", chunkId] as const, + }, auth: { user: ["auth", "user"] as const, },